Un modèle de fonction membre de classe C ++ peut-il être virtuel?

J’ai entendu dire que les modèles de fonction membres de classe C ++ ne pouvaient pas être virtuels. Est-ce vrai?

S’ils peuvent être virtuels, quel exemple de scénario utiliserait-il?

Les modèles concernent le compilateur générant du code au moment de la compilation . Les fonctions virtuelles sont tout au sujet du système d’exécution déterminant quelle fonction appeler à l’ exécution .

Une fois que le système d’exécution s’est rendu compte qu’il aurait besoin d’appeler une fonction virtuelle modélisée, la compilation est terminée et le compilateur ne peut plus générer l’instance appropriée. Par conséquent, vous ne pouvez pas avoir de modèles de fonction de membre virtuel.

Cependant, il existe quelques techniques puissantes et intéressantes résultant de la combinaison de polymorphism et de modèles, notamment de type d’effacement .

À partir de modèles C ++ Le guide complet:

Les modèles de fonction membres ne peuvent pas être déclarés virtuels. Cette contrainte est imposée car l’implémentation habituelle du mécanisme d’appel de la fonction virtuelle utilise une table de taille fixe avec une entrée par fonction virtuelle. Cependant, le nombre d’instanciations d’un modèle de fonction membre n’est pas fixe tant que le programme entier n’a pas été traduit. Par conséquent, la prise en charge de modèles de fonction membres virtuels nécessiterait la prise en charge d’un tout nouveau type de mécanisme dans les compilateurs et les lieurs C ++. En revanche, les membres ordinaires des modèles de classe peuvent être virtuels car leur nombre est fixe lorsqu’une classe est instanciée.

C ++ n’autorise pas les fonctions des membres de modèles virtuels pour le moment. La raison la plus probable est la complexité de sa mise en œuvre. Rajendra donne une bonne raison pour laquelle cela ne peut pas être fait maintenant, mais cela pourrait être possible avec des changements raisonnables de la norme. Surtout en ce qui concerne le nombre d’instanciations d’une fonction basée sur un modèle, la création de la vtable semble difficile si l’on considère la place de l’appel de fonction virtuelle. Les normalisateurs ont beaucoup d’autres choses à faire en ce moment et C ++ 1x est également très utile pour les auteurs du compilateur.

Quand auriez-vous besoin d’une fonction membre basée sur un modèle? Une fois, j’ai rencontré une telle situation où j’ai essayé de refactoriser une hiérarchie avec une classe de base virtuelle pure. C’était un style médiocre de mettre en œuvre différentes stratégies. Je voulais changer l’argument de l’une des fonctions virtuelles en un type numérique et au lieu de surcharger la fonction membre et remplacer chaque surcharge de toutes les sous-classes, j’ai essayé d’utiliser des fonctions de modèle virtuel (et de découvrir qu’elles n’existent pas) .)

Tables de fonction virtuelles

Commençons par quelques informations sur les tables de fonctions virtuelles et leur fonctionnement ( source ):

[20.3] Quelle est la différence entre l’appel des fonctions membres virtuelles et non virtuelles?

Les fonctions membres non virtuelles sont résolues statiquement. C’est-à-dire que la fonction membre est sélectionnée de manière statique (à la compilation) en fonction du type du pointeur (ou de la référence) de l’object.

En revanche, les fonctions de membre virtuel sont résolues de manière dynamic (au moment de l’exécution). En d’autres termes, la fonction membre est sélectionnée dynamicment (au moment de l’exécution) en fonction du type d’object et non du type du pointeur / référence à cet object. Ceci est appelé “liaison dynamic”. La plupart des compilateurs utilisent une variante de la technique suivante: si l’object possède une ou plusieurs fonctions virtuelles, le compilateur place un pointeur caché dans l’object appelé “pointeur virtuel” ou “pointeur v”. Ce pointeur v pointe vers une table globale appelée “table virtuelle” ou “table v”.

Le compilateur crée une v-table pour chaque classe ayant au moins une fonction virtuelle. Par exemple, si la classe Circle a des fonctions virtuelles pour draw () et move () et resize (), il y aurait exactement une v-table associée à la classe Circle, même s’il y avait des objects Cercle gazillion et le pointeur v de chacun de ces objects Cercle pointerait sur la v-table Circle. La v-table elle-même contient des pointeurs vers chacune des fonctions virtuelles de la classe. Par exemple, la v-table Circle aura trois pointeurs: un pointeur sur Circle :: draw (), un pointeur sur Circle :: move () et un pointeur sur Circle :: resize ().

Lors d’une dissortingbution d’une fonction virtuelle, le système d’exécution suit le pointeur v de l’object vers la v-table de la classe, puis suit l’emplacement approprié dans la v-table vers le code de la méthode.

La surcharge d’espace-coût de la technique ci-dessus est nominale: un pointeur supplémentaire par object (mais uniquement pour les objects nécessitant une liaison dynamic), plus un pointeur supplémentaire par méthode (mais uniquement pour les méthodes virtuelles). La surcharge temporelle est également assez nominale: comparé à un appel de fonction normal, un appel de fonction virtuel nécessite deux extractions supplémentaires (une pour obtenir la valeur du pointeur v, une seconde pour obtenir l’adresse de la méthode). Aucune de ces activités d’exécution ne se produit avec des fonctions non virtuelles, car le compilateur résout les fonctions non virtuelles exclusivement à la compilation en fonction du type du pointeur.


Mon problème ou comment je suis venu ici

J’essaie maintenant d’utiliser quelque chose comme ça pour une classe de base cubefile avec des fonctions de chargement optimisées basées sur des modèles qui seront implémentées différemment pour différents types de cubes (certains stockés par pixel, certains par image, etc.).

Un code:

 virtual void LoadCube(UtpBipCube &Cube,long LowerLeftRow=0,long LowerLeftColumn=0, long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0; virtual void LoadCube(UtpBipCube &Cube, long LowerLeftRow=0,long LowerLeftColumn=0, long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0; virtual void LoadCube(UtpBipCube &Cube, long LowerLeftRow=0,long LowerLeftColumn=0, long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0; 

Ce que je voudrais qu’il soit, mais il ne comstackra pas à cause d’un combo virtuel basé sur des modèles:

 template virtual void LoadCube(UtpBipCube &Cube,long LowerLeftRow=0,long LowerLeftColumn=0, long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0; 

J’ai fini par déplacer la déclaration de modèle au niveau de la classe . Cette solution aurait forcé les programmes à connaître les types de données spécifiques qu’ils liraient avant de les lire, ce qui est inacceptable.

Solution

attention, ce n’est pas très joli mais cela m’a permis de supprimer du code d’exécution répétitif

1) dans la classe de base

 virtual void LoadCube(UtpBipCube &Cube,long LowerLeftRow=0,long LowerLeftColumn=0, long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0; virtual void LoadCube(UtpBipCube &Cube, long LowerLeftRow=0,long LowerLeftColumn=0, long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0; virtual void LoadCube(UtpBipCube &Cube, long LowerLeftRow=0,long LowerLeftColumn=0, long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0; 

2) et dans les classes pour enfants

 void LoadCube(UtpBipCube &Cube, long LowerLeftRow=0,long LowerLeftColumn=0, long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) { LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); } void LoadCube(UtpBipCube &Cube, long LowerLeftRow=0,long LowerLeftColumn=0, long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) { LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); } void LoadCube(UtpBipCube &Cube, long LowerLeftRow=0,long LowerLeftColumn=0, long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) { LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); } template void LoadAnyCube(UtpBipCube &Cube, long LowerLeftRow=0,long LowerLeftColumn=0, long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1); 

Notez que LoadAnyCube n’est pas déclaré dans la classe de base.


Voici une autre réponse de débordement de stack avec un travail: besoin d’une solution de contournement de membre de modèle virtuel .

Le code suivant peut être compilé et exécuté correctement, en utilisant MinGW G ++ 3.4.5 sur la fenêtre 7:

 #include  #include  using namespace std; template  class A{ public: virtual void func1(const T& p) { cout<<"A:"< class B : public A { public: virtual void func1(const T& p) { cout<<"A<--B:"< a; B b; B c; A* p = &a; p->func1("A a"); p = dynamic_cast*>(&c); p->func1("B c"); B* q = &b; q->func1(3); } 

et la sortie est la suivante:

 A:A a A<--B:B c A<--B:3 

Et plus tard, j'ai ajouté une nouvelle classe X:

 class X { public: template  virtual void func2(const T& p) { cout<<"C:"< 

Quand j'ai essayé d'utiliser la classe X dans main () comme ceci:

 X x; x.func2("X x"); 

g ++ signale l'erreur suivante:

 vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu al void X::func2(const T&)' 

Donc, il est évident que:

  • la fonction de membre virtuel peut être utilisée dans un modèle de classe. Il est facile pour le compilateur de construire vtable
  • Il est impossible de définir une fonction membre de modèle de classe en tant que virtuelle, comme vous pouvez le voir, il est difficile de déterminer la signature de la fonction et d’allouer des entrées vtable.

Non ils ne peuvent pas. Mais:

 template class Foo { public: template void f(const P& p) { ((T*)this)->f

(p); } }; class Bar : public Foo { public: template void f(const P& p) { std::cout << p << std::endl; } }; int main() { Bar bar; Bar *pbar = &bar; pbar -> f(1); Foo *pfoo = &bar; pfoo -> f(1); };

a le même effet si tout ce que vous voulez faire est d’avoir une interface commune et de reporter l’implémentation aux sous-classes.

Pour répondre à la deuxième partie de la question:

S’ils peuvent être virtuels, quel exemple de scénario utiliserait-il?

Ce n’est pas une chose déraisonnable que de vouloir faire. Par exemple, Java (où chaque méthode est virtuelle) n’a pas de problèmes avec les méthodes génériques.

Un exemple en C ++ de vouloir un modèle de fonction virtuel est une fonction membre qui accepte un iterator générique. Ou une fonction membre qui accepte un object fonction générique.

La solution à ce problème consiste à utiliser l’effacement de type avec boost :: any_range et boost :: function, ce qui vous permettra d’accepter un iterator ou un foncteur générique sans avoir à faire de votre fonction un modèle.

Il existe une solution de contournement pour la «méthode de modèle virtuel» si un ensemble de types pour la méthode de modèle est connu à l’avance.

Pour montrer l’idée, dans l’exemple ci-dessous, seuls deux types sont utilisés ( int et double ).

Là, une méthode de modèle «virtuelle» ( Base::Method ) appelle la méthode virtuelle correspondante (une de Base::VMethod ) qui, à son tour, appelle l’implémentation de la méthode de modèle ( Impl::TMethod ).

Il suffit d’implémenter la méthode template TMethod dans les implémentations dérivées ( AImpl , BImpl ) et d’utiliser Derived<*Impl> .

 class Base { public: virtual ~Base() { } template  T Method(T t) { return VMethod(t); } private: virtual int VMethod(int t) = 0; virtual double VMethod(double t) = 0; }; template  class Derived : public Impl { public: template  Derived(TArgs&&... args) : Impl(std::forward(args)...) { } private: int VMethod(int t) final { return Impl::TMethod(t); } double VMethod(double t) final { return Impl::TMethod(t); } }; class AImpl : public Base { protected: AImpl(int p) : i(p) { } template  T TMethod(T t) { return t - i; } private: int i; }; using A = Derived; class BImpl : public Base { protected: BImpl(int p) : i(p) { } template  T TMethod(T t) { return t + i; } private: int i; }; using B = Derived; int main(int argc, const char* argv[]) { A a(1); B b(1); Base* base = nullptr; base = &a; std::cout << base->Method(1) << std::endl; std::cout << base->Method(2.0) << std::endl; base = &b; std::cout << base->Method(1) << std::endl; std::cout << base->Method(2.0) << std::endl; } 

Sortie:

 0 1 2 3 

NB: Base::Method est en réalité excédentaire pour le code réel ( VMethod peut être rendu public et utilisé directement). Je l'ai ajouté pour que cela ressemble à une méthode de modèle "virtuelle".

Non, les fonctions membres du modèle ne peuvent pas être virtuelles.

Au moins avec gcc 5.4, les fonctions virtuelles pourraient être des membres de template mais doivent être des templates eux-mêmes.

 #include  #include  class first { protected: virtual std::ssortingng a1() { return "a1"; } virtual std::ssortingng mixt() { return a1(); } }; class last { protected: virtual std::ssortingng a2() { return "a2"; } }; template class mix: first , T { public: virtual std::ssortingng mixt() override; }; template std::ssortingng mix::mixt() { return a1()+" before "+T::a2(); } class mix2: public mix { virtual std::ssortingng a1() override { return "mix"; } }; int main() { std::cout << mix2().mixt(); return 0; } 

Les sorties

 mix before a2 Process finished with exit code 0 

Dans les autres réponses, la fonction de modèle proposée est une façade et n’offre aucun avantage pratique.

  • Les fonctions de modèle sont utiles pour écrire du code une seule fois en utilisant différents types.
  • Les fonctions virtuelles sont utiles pour avoir une interface commune pour différentes classes.

Le langage n’autorise pas les fonctions de modèle virtuel, mais avec une solution de contournement, il est possible d’avoir les deux, par exemple une implémentation de modèle pour chaque classe et une interface commune virtuelle.

Il est toutefois nécessaire de définir pour chaque combinaison de type de gabarit une fonction de wrapper virtuel factice:

 #include  #include  #include  //--------------------------------------------- // Abstract class with virtual functions class Geometry { public: virtual void getArea(float &area) = 0; virtual void getArea(long double &area) = 0; }; //--------------------------------------------- // Square class Square : public Geometry { public: float size {1}; // virtual wrapper functions call template function for square virtual void getArea(float &area) { getAreaT(area); } virtual void getArea(long double &area) { getAreaT(area); } private: // Template function for squares template  void getAreaT(T &area) { area = static_cast(size * size); } }; //--------------------------------------------- // Circle class Circle : public Geometry { public: float radius {1}; // virtual wrapper functions call template function for circle virtual void getArea(float &area) { getAreaT(area); } virtual void getArea(long double &area) { getAreaT(area); } private: // Template function for Circles template  void getAreaT(T &area) { area = static_cast(radius * radius * 3.1415926535897932385L); } }; //--------------------------------------------- // Main int main() { // get area of square using template based function T=float std::unique_ptr geometry = std::make_unique(); float areaSquare; geometry->getArea(areaSquare); // get area of circle using template based function T=long double geometry = std::make_unique(); long double areaCircle; geometry->getArea(areaCircle); std::cout << std::setprecision(20) << "Square area is " << areaSquare << ", Circle area is " << areaCircle << std::endl; return 0; } 

Sortie:

La surface carrée est 1, la zone du cercle est 3.1415926535897932385

Essayez-le ici