Cómo definir diferentes tipos para la misma clase en C ++

84

Me gustaría tener varios tipos que compartan la misma implementación pero que sigan siendo de diferente tipo en C ++.

Para ilustrar mi pregunta con un ejemplo simple, me gustaría tener una clase para Manzanas, Naranjas y Plátanos, todas con las mismas operaciones y la misma implementación. Me gustaría que tuvieran diferentes tipos porque quiero evitar errores gracias a la seguridad de tipos.

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 no duplicar el código, parece que podría usar una clase base Fruit y heredar de ella:

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 {};

Pero entonces, los constructores no se heredan y tengo que reescribirlos.

¿Existe algún mecanismo (typedefs, templates, herencia ...) que me permita tener fácilmente la misma clase con diferentes tipos?

anumi
fuente
¿Puede explicar con más detalle por qué necesitaría esto? No se me ocurre ninguna buena idea. Si las clases comparten la implementación, ¿no significa eso que también comparten la funcionalidad?
jnovacho
4
Sí, pero dado que tendrán diferentes tipos, se pueden detectar algunos errores de programación en el momento de la compilación (por ejemplo, fusionando manzanas y naranjas).
anumi

Respuestas:

119

Una técnica común es tener una plantilla de clase donde el argumento de la plantilla simplemente sirve como un token único ("etiqueta") para convertirlo en un 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>;

Tenga en cuenta que las clases de etiquetas ni siquiera necesitan estar definidas, es suficiente declarar un nombre de tipo único. Esto funciona porque la etiqueta no se usa en ninguna parte de la plantilla. Y puede declarar el nombre del tipo dentro de la lista de argumentos de la plantilla (sugerencia a @Xeo).

La usingsintaxis es C ++ 11. Si está atascado con C ++ 03, escriba esto en su lugar:

typedef Fruit<struct AppleTag> Apple;

Si la funcionalidad común requiere mucho código, desafortunadamente, esto introduce una gran cantidad de código duplicado en el ejecutable final. Esto se puede evitar teniendo una clase base común que implemente la funcionalidad y luego teniendo una especialización (que en realidad instancia) que se deriva de ella.

Desafortunadamente, eso requiere que vuelva a implementar todos los miembros no heredables (constructores, asignación…) lo que agrega una pequeña sobrecarga en sí misma, por lo que esto solo tiene sentido para clases grandes. Aquí se aplica al ejemplo anterior:

// 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
fuente
+1, iría con esto si no desea tener propiedades adicionales definidas para las frutas individuales ...
Nim
20
Usted puede simplemente declarar dentro de la lista de argumentos de plantilla, que me parece muy conveniente: Fruit<struct SomeTag>.
Xeo
1
@KonradRudolph es una pena que no pueda hacer +1 en la edición en sí ... Vi ese comentario de edición .
Eternalmatt
1
@eternalmatt LOL - Nunca hubiera pensado que alguien vería eso. Pero bueno, tienes que ser gracioso incluso cuando nadie está mirando. ;-)
Konrad Rudolph
2
Una desventaja de esto es la emisión múltiple de la instanciación de la plantilla para los diferentes tipos. ¿Son estos duplicados eliminados por enlazadores ampliamente utilizados?
boycy
19

Use plantillas y use un rasgo por fruta, por ejemplo:

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

Luego tenga una sola Fruitclase que se escriba en este rasgo, por ejemplo.

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;

¡Puede ser un poco exagerado! ;)

Nim
fuente
14
  • C ++ 11 permitiría la herencia de constructores: ¿ usa constructores de clase base C ++?
  • De lo contrario, puede usar plantillas para lograr lo mismo, por ejemplo template<class Derived> class Fruit;
Sam
fuente