Como definir tipos diferentes para a mesma classe em C ++

84

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?

anumi
fonte
Você pode explicar com mais detalhes por que você precisa disso? Não consigo ter nenhuma boa ideia. Se as classes compartilham implementação, isso não significa que também compartilham funcionalidade?
jnovacho de
4
Sim, mas como eles terão tipos diferentes, alguns erros de programação podem ser detectados no momento da compilação (por exemplo, fusão de maçãs e laranjas).
anumi

Respostas:

119

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 usingsintaxe é 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>;
Konrad Rudolph
fonte
+1, eu iria com isso se você não quiser ter nenhuma propriedade adicional definida para as frutas individuais ...
Nim
20
Você pode realmente apenas declará-los dentro da lista de argumentos modelo, o que eu acho bastante conveniente: Fruit<struct SomeTag>.
Xeo
1
@KonradRudolph é uma pena que não posso marcar com +1 a edição em si ... Eu vi aquele comentário de edição .
eternalmatt
1
@eternalmatt LOL - Eu nunca teria pensado que alguém veria isso. Mas bem, você tem que ser engraçado mesmo quando ninguém está olhando. ;-)
Konrad Rudolph
2
Uma desvantagem disso é a emissão múltipla da instanciação do modelo para os diferentes tipos. Essas duplicatas são eliminadas por linkers amplamente usados?
menino de
19

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 Fruitclasse 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! ;)

Nim
fonte