83 votes

Comment définir différents types pour la même classe en C++ ?

J'aimerais avoir plusieurs types qui partagent la même implémentation mais qui sont toujours de type différent en C++.

Pour illustrer ma question par un exemple simple, j'aimerais avoir une classe pour les pommes, les oranges et les bananes, toutes ayant les mêmes opérations et la même implémentation. J'aimerais qu'elles aient des types différents car je veux éviter les erreurs grâce à la sécurité des types.

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 ...

Afin de ne pas dupliquer le code, il semble que je puisse utiliser une classe de base Fruit et en hériter :

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

Mais alors, les constructeurs ne sont pas hérités et je dois les réécrire.

Existe-t-il un mécanisme (typedefs, templates, héritage...) qui me permettrait d'avoir facilement la même classe avec des types différents ?

118voto

Konrad Rudolph Points 231505

Une technique courante consiste à disposer d'un modèle de classe dont l'argument sert simplement de jeton unique ("tag") pour en faire un type unique :

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

Notez que les classes de balises n'ont même pas besoin d'être définies, il suffit de déclarer un nom de type unique. Cela fonctionne parce que la balise n'est pas réellement utilisé n'importe où dans le modèle. Et vous pouvez déclarer le nom du type à l'intérieur la liste des arguments du modèle (chapeau à @Xeo).

En using est C++11. Si vous êtes coincé avec C++03, écrivez ceci à la place :

typedef Fruit<struct AppleTag> Apple;

Si la fonctionnalité commune occupe une grande partie du code, cela introduit malheureusement beaucoup de code en double dans l'exécutable final. Cela peut être évité en ayant une classe de base commune implémentant la fonctionnalité, puis en ayant une spécialisation (que vous instanciez réellement) qui en dérive.

Malheureusement, cela vous oblige à réimplémenter tous les membres non héritables (constructeurs, affectation ), ce qui ajoute un léger surcoût - cela n'a donc de sens que pour les classes de grande taille. Voici l'application de cette méthode à l'exemple ci-dessus :

// 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>;

19voto

Nim Points 22570

Utilisez des modèles, et utilisez un trait par fruit, par exemple :

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

Ensuite, il y a un seul Fruit qui est typée sur ce trait, par exemple.

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;

C'est peut-être un peu exagéré ! ;)

14voto

Sam Points 5878

6voto

StackedCrooked Points 12247

Il y a aussi BOOST_STRONG_TYPEDEF .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X