Eu gostaria de ter vários tipos que compartilham a mesma implementação, mas ainda são de tipos diferentes em C ++.
Para ilustrar minha pergunta com um exemplo simples, gostaria de ter uma aula de Maçãs, Laranjas e Bananas, todas com as mesmas operações e a mesma implementação. Eu gostaria que eles tivessem tipos diferentes, porque quero evitar erros graças à segurança de tipo.
class Apple {
int p;
public:
Apple (int p) : p(p) {}
int price () const {return p;}
}
class Banana {
int p;
public:
Banana (int p) : p(p) {}
int price () const {return p;}
}
class Orange ...
Para não duplicar o código, parece que eu poderia usar uma classe base Fruit e herdar dela:
class Fruit {
int p;
public:
Fruit (int p) : p(p) {}
int price () const {return p;}
}
class Apple: public Fruit {};
class Banana: public Fruit {};
class Orange: public Fruit {};
Mas então, os construtores não são herdados e eu tenho que reescrevê-los.
Existe algum mecanismo (typedefs, modelos, herança ...) que me permitiria facilmente ter a mesma classe com tipos diferentes?
Respostas:
Uma técnica comum é ter um modelo de classe em que o argumento do modelo serve simplesmente como um token único (“tag”) para torná-lo um tipo único:
template <typename Tag> class Fruit { int p; public: Fruit(int p) : p(p) { } int price() const { return p; } }; using Apple = Fruit<struct AppleTag>; using Banana = Fruit<struct BananaTag>;
Observe que as classes de tag nem precisam ser definidas, é suficiente declarar um nome de tipo único. Isso funciona porque a tag não é realmente usada em qualquer lugar do modelo. E você pode declarar o nome do tipo dentro da lista de argumentos do modelo (gorjeta para @Xeo).
A
using
sintaxe é C ++ 11. Se você estiver preso ao C ++ 03, escreva isto:typedef Fruit<struct AppleTag> Apple;
Se a funcionalidade comum ocupa muito código, isso infelizmente introduz uma grande quantidade de código duplicado no executável final. Isso pode ser evitado tendo uma classe base comum implementando a funcionalidade e, em seguida, tendo uma especialização (que você realmente instancia) que deriva dela.
Infelizmente, isso requer que você reimplemente todos os membros não herdáveis (construtores, atribuição ...), o que adiciona uma pequena sobrecarga por si só - então isso só faz sentido para classes grandes. Aqui, ele é aplicado ao exemplo acima:
// Actual `Fruit` class remains unchanged, except for template declaration template <typename Tag, typename = Tag> class Fruit { /* unchanged */ }; template <typename T> class Fruit<T, T> : public Fruit<T, void> { public: // Should work but doesn’t on my compiler: //using Fruit<T, void>::Fruit; Fruit(int p) : Fruit<T, void>(p) { } }; using Apple = Fruit<struct AppleTag>; using Banana = Fruit<struct BananaTag>;
fonte
Fruit<struct SomeTag>
.Use modelos e use uma característica por fruta, por exemplo:
struct AppleTraits { // define apple specific traits (say, static methods, types etc) static int colour = 0; }; struct OrangeTraits { // define orange specific traits (say, static methods, types etc) static int colour = 1; }; // etc
Então, tenha uma única
Fruit
classe que é digitada nesta característica, por exemplo.template <typename FruitTrait> struct Fruit { // All fruit methods... // Here return the colour from the traits class.. int colour() const { return FruitTrait::colour; } }; // Now use a few typedefs typedef Fruit<AppleTraits> Apple; typedef Fruit<OrangeTraits> Orange;
Pode ser um pouco exagero! ;)
fonte
template<class Derived> class Fruit;
fonte
Também existe BOOST_STRONG_TYPEDEF .
fonte