Polymorphisme des modèles C ++

J’ai cette structure de cours.

class Interface{ ... } class Foo : public Interface{ ... } template  class Container{ ... } 

Et j’ai ce constructeur d’une autre classe Bar.

 Bar(const Container & bar){ ... } 

Lorsque j’appelle le constructeur de cette façon, l’erreur “Aucune fonction correspondante” est générée.

 Container container (); Bar * temp = new Bar(container); 

Qu’est-ce qui ne va pas? Les modèles ne sont-ils pas polymorphes?

Je pense que la terminologie exacte de ce dont vous avez besoin est “modèle de covariance”, ce qui signifie que si B hérite de A, alors T hérite de T . Ce n’est pas le cas en C ++, ni avec les génériques Java et C # *.

Il y a une bonne raison d’éviter la covariance de modèle: cela supprimera simplement toute la sécurité de type dans la classe de modèle. Laissez-moi vous expliquer avec l’exemple suivant:

 //Assume the following class hierarchy class Fruit {...}; class Apple : public Fruit {...}; class Orange : public Fruit {...}; //Now I will use these types to instantiate a class template, namely std::vector int main() { std::vector apple_vec; apple_vec.push_back(Apple()); //no problem here //If templates were covariant, the following would be legal std::vector & fruit_vec = apple_vec; //push_back would expect a Fruit, so I could pass it an Orange fruit_vec.push_back(Orange()); //Oh no! I just added an orange in my apple basket! } 

Par conséquent, vous devez considérer T et T comme des types complètement non liés, quelle que soit la relation entre A et B.

Alors, comment pourriez-vous résoudre le problème auquel vous êtes confronté? En Java et en C #, vous pouvez utiliser respectivement des caractères génériques liés et des contraintes :

 //Java code Bar(Container(Container container) where T : Interface {...} 

La prochaine norme C ++ (connue sous le nom de C ++ 1x (anciennement C ++ 0x)) contenait initialement un mécanisme encore plus puissant appelé Concepts , qui aurait permis aux développeurs de faire respecter des exigences syntaxiques et / ou sémantiques sur les parameters de modèle, mais qui a malheureusement été reporté à une date ultérieure. Cependant, Boost a une bibliothèque Concept Check qui peut vous intéresser.

Néanmoins, les concepts peuvent être un peu excessifs pour le problème que vous rencontrez, l’utilisation d’une simple assertion statique telle que proposée par @gf est probablement la meilleure solution.

* Mise à jour: Depuis .Net Framework 4, il est possible de marquer des parameters génériques comme étant covariants ou contravariants .

Il y a deux problèmes ici: les constructions par défaut ont la forme MyClass c; ; avec des parenthèses, cela ressemble à une déclaration de fonction au compilateur.

L’autre problème est que Container est simplement un type différent de Container – vous pouvez procéder comme suit pour obtenir un polymorphism:

 Bar::Bar(const Container&) {} Container container; container.push_back(new Foo); Bar* temp = new Bar(container); 

Vous pouvez également faire de Bar ou de son constructeur un gabarit, comme l’a montré Kornel.

Si vous voulez réellement un polymorphism au moment de la compilation, vous pouvez utiliser Boost.TypeTraits is_base_of ou un équivalent:

 template Bar::Bar(const Container& c) { BOOST_STATIC_ASSERT((boost::is_base_of::value)); // ... will give a comstack time error if T doesn't // inherit from Interface } 

Imaginez que le paramètre de conteneur soit “codé en dur” dans la classe qu’il définit (et c’est ainsi que cela fonctionne). Par conséquent, le type de Container_Foo est Container_Foo , ce qui n’est pas compatible avec Container_Interface .

Voici ce que vous pourriez essayer:

 template Bar(const Container & bar){ ... } 

Pourtant, vous perdez la vérification directe de ce type.

En réalité, la méthode STL (probablement plus efficace et générique) consisterait à faire

 template Bar(InputIterator begin, InputIterator end){ ... } 

… mais je suppose que vous n’avez pas d’iterators implémentés dans le conteneur.

Il est possible de créer une arborescence d’inheritance pour les conteneurs, reflétant l’arborescence d’inheritance des données. Si vous avez les données suivantes:

 class Interface { public: virtual ~Interface() {} virtual void print() = 0; }; class Number : public Interface { public: Number(int value) : x( value ) {} int get() const { return x; } void print() { std::printf( "%d\n", get() ); }; private: int x; }; class Ssortingng : public Interface { public: Ssortingng(const std::ssortingng & value) : x( value ) {} const std::ssortingng &get() const { return x; } void print() { std::printf( "%s\n", get().c_str() ); } private: std::ssortingng x; }; 

Vous pouvez également avoir les conteneurs suivants:

 class GenericContainer { public: GenericContainer() {} ~GenericContainer() { v.clear(); } virtual void add(Interface &obj) { v.push_back( &obj ); } Interface &get(unsigned int i) { return *v[ i ]; } unsigned int size() const { return v.size(); } private: std::vector v; }; class NumericContainer : public GenericContainer { public: virtual void add(Number &obj) { GenericContainer::add( obj ); } Number &get(unsigned int i) { return (Number &) GenericContainer::get( i ); } }; class TextContainer : public GenericContainer { public: virtual void add(Ssortingng &obj) { GenericContainer::add( obj ); } Ssortingng &get(unsigned int i) { return (Ssortingng &) GenericContainer::get( i ); } }; 

Ce n’est pas le code le plus performant; c’est juste pour donner une idée. Le seul problème avec cette approche est que chaque fois que vous ajoutez une nouvelle classe de données, vous devez également créer un nouveau conteneur. En dehors de cela, vous avez un polymorphism “qui fonctionne à nouveau”. Vous pouvez être spécifique ou général:

 void print(GenericContainer & x) { for(unsigned int i = 0; i < x.size(); ++i) { x.get( i ).print(); } } void printNumbers(NumericContainer & x) { for(unsigned int i = 0; i < x.size(); ++i) { printf( "Number: " ); x.get( i ).print(); } } int main() { TextContainer strContainer; NumericContainer numContainer; Number n( 345 ); String s( "Hello" ); numContainer.add( n ); strContainer.add( s ); print( strContainer ); print( numContainer ); printNumbers( numContainer ); } 

Je propose la solution de contournement suivante, qui utilise une fonction de modèle. Bien que l’exemple utilise QList de Qt, rien n’empêche la solution d’être directement transposée dans un autre conteneur.

 template  // D (Derived) inherits from B (Base) QList toBaseList(QList derivedList) { QList baseList; for (int i = 0; i < derivedList.size(); ++i) { baseList.append(derivedList[i]); } return baseList; } 

Avantages:

  • général
  • type-safe
  • assez efficace si les éléments sont des pointeurs ou d'autres éléments constructibles en copie à moindre coût (tels que des classes Qt implicitement partagées)

Les inconvénients:

  • nécessite la création d'un nouveau conteneur, au lieu de permettre la réutilisation du conteneur d'origine
  • implique un peu de mémoire et de temps processeur pour la création et le remplissage du nouveau conteneur, ce qui dépend fortement du coût du constructeur de copie

conteneur est un conteneur d’objects Foo et non un conteneur d’objects Interface

Et il ne peut pas non plus être polymorphe, les pointeurs sur les choses peuvent être, mais pas les objects eux-mêmes. Quelle doit être la taille des emplacements dans le conteneur si vous pouvez y insérer des éléments dérivés de l’interface?

vous avez besoin

  container 

ou mieux

  container >