J'ai entendu dire que les modèles de fonction de membre de classe C ++ ne peuvent pas être virtuels. Est-ce vrai?
S'ils peuvent être virtuels, quel est un exemple de scénario dans lequel on utiliserait une telle fonction?
J'ai entendu dire que les modèles de fonction de membre de classe C ++ ne peuvent pas être virtuels. Est-ce vrai?
S'ils peuvent être virtuels, quel est un exemple de scénario dans lequel on utiliserait une telle fonction?
Réponses:
Les modèles sont tous sur le compilateur générant du code au moment de la compilation . Les fonctions virtuelles concernent le système d'exécution qui détermine la fonction à appeler lors de l' exécution .
Une fois que le système d'exécution a compris 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 issues de la combinaison du polymorphisme et des modèles, notamment ce que l'on appelle l' effacement de type .
Virtual functions are all about the run-time system figuring out which function to call at run-time
- désolé mais c'est une façon plutôt fausse de le faire, et assez déroutant. C'est juste une indirection, et il n'y a pas de "calcul de l'exécution" impliqué, il est connu pendant la compilation que la fonction à appeler est celle pointée par le n-ième pointeur de la table. "Comprendre" implique qu'il existe des vérifications de type et autres, ce qui n'est pas le cas. Once the run-time system figured out it would need to call a templatized virtual function
- si la fonction est virtuelle ou non est connue au moment de la compilation.
void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }
, alors il "sait" pour quelle fonction est invoquée au point cb.f()
appelé et ne le sait pas pour vb.f()
. Ce dernier doit être découvert lors de l'exécution , par le système d'exécution . Que vous vouliez appeler cela "comprendre", et que ce soit plus ou moins efficace, cela ne change pas un peu ces faits.
À partir de modèles C ++ Le guide complet:
Les modèles de fonction membre ne peuvent pas être déclarés virtuels. Cette contrainte est imposée car l'implémentation habituelle du mécanisme d'appel de 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 fixé tant que le programme entier n'a pas été traduit. Par conséquent, la prise en charge des modèles de fonction de membre virtuel nécessiterait la prise en charge d'un tout nouveau type de mécanisme dans les compilateurs et les éditeurs de liens 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 actuellement les fonctions de membre de modèle virtuel. 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 en ce moment, mais cela pourrait être possible avec des changements raisonnables de la norme. Il est particulièrement difficile de déterminer le nombre d'instanciations d'une fonction basée sur un modèle et de créer la table virtuelle si vous considérez la place de l'appel de fonction virtuelle. Les normalisateurs ont juste beaucoup d'autres choses à faire en ce moment et C ++ 1x est aussi beaucoup de travail pour les rédacteurs du compilateur.
Quand auriez-vous besoin d'une fonction membre basée sur des modèles? J'ai rencontré une fois une telle situation où j'ai essayé de refaçonner une hiérarchie avec une classe de base virtuelle pure. C'était un style médiocre pour 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 de remplacer chaque surcharge dans toutes les sous-classes, j'ai essayé d'utiliser des fonctions de modèle virtuel (et j'ai dû découvrir qu'elles n'existent pas .)
Commençons par quelques informations sur les tables de fonctions virtuelles et leur fonctionnement ( source ):
[20.3] Quelle est la différence entre la façon dont les fonctions membres virtuelles et non virtuelles sont appelées?
Les fonctions membres non virtuelles sont résolues statiquement. Autrement dit, la fonction membre est sélectionnée statiquement (au moment de la compilation) en fonction du type du pointeur (ou de la référence) vers l'objet.
En revanche, les fonctions de membre virtuel sont résolues dynamiquement (au moment de l'exécution). Autrement dit, la fonction membre est sélectionnée dynamiquement (au moment de l'exécution) en fonction du type de l'objet, et non du type du pointeur / de la référence à cet objet. C'est ce qu'on appelle la «liaison dynamique». La plupart des compilateurs utilisent une variante de la technique suivante: si l'objet a une ou plusieurs fonctions virtuelles, le compilateur place un pointeur caché dans l'objet appelé "pointeur virtuel" ou "pointeur v". Ce pointeur en v pointe vers une table globale appelée «table virtuelle» ou «table en v».
Le compilateur crée une v-table pour chaque classe qui a 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 table en V associée à la classe Circle, même s'il y avait un gazillion d'objets Circle et le pointeur en V de chacun de ces objets Circle pointerait vers la table en v Circle. La v-table elle-même a des pointeurs vers chacune des fonctions virtuelles de la classe. Par exemple, la v-table Circle aurait trois pointeurs: un pointeur sur Circle :: draw (), un pointeur sur Circle :: move () et un pointeur sur Circle :: resize ().
Lors de l'envoi d'une fonction virtuelle, le système d'exécution suit le pointeur v de l'objet vers la table v de la classe, puis suit l'emplacement approprié dans la table v jusqu'au code de méthode.
Le surcoût en termes d'espace de la technique ci-dessus est nominal: un pointeur supplémentaire par objet (mais uniquement pour les objets qui devront effectuer une liaison dynamique), plus un pointeur supplémentaire par méthode (mais uniquement pour les méthodes virtuelles). La surcharge de temps-coût est également assez nominale: par rapport à un appel de fonction normal, un appel de fonction virtuelle nécessite deux récupérations supplémentaires (une pour obtenir la valeur du pointeur v, une seconde pour obtenir l'adresse de la méthode). Aucune de cette activité d'exécution ne se produit avec les fonctions non virtuelles, car le compilateur résout les fonctions non virtuelles exclusivement au moment de la compilation en fonction du type du pointeur.
J'essaie d'utiliser quelque chose comme ça maintenant pour une classe de base de fichiers cubiques 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.).
Du code:
virtual void LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
Ce que j'aimerais que ce soit, mais il ne se compilera pas en raison d'un combo basé sur un modèle virtuel:
template<class T>
virtual void LoadCube(UtpBipCube<T> &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 obligé les programmes à connaître les types spécifiques de données qu'ils liraient avant de les lire, ce qui est inacceptable.
avertissement, ce n'est pas très joli mais cela m'a permis de supprimer le code d'exécution répétitive
1) dans la classe de base
virtual void LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void LoadCube(UtpBipCube<unsigned short> &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 enfants
void LoadCube(UtpBipCube<float> &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<short> &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<unsigned short> &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<class T>
void LoadAnyCube(UtpBipCube<T> &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épassement de pile avec une solution: besoin d'une solution de contournement de membre de modèle virtuel .
Le code suivant peut être compilé et s'exécute correctement, à l'aide de MinGW G ++ 3.4.5 sur Windows 7:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
class A{
public:
virtual void func1(const T& p)
{
cout<<"A:"<<p<<endl;
}
};
template <typename T>
class B
: public A<T>
{
public:
virtual void func1(const T& p)
{
cout<<"A<--B:"<<p<<endl;
}
};
int main(int argc, char** argv)
{
A<string> a;
B<int> b;
B<string> c;
A<string>* p = &a;
p->func1("A<string> a");
p = dynamic_cast<A<string>*>(&c);
p->func1("B<string> c");
B<int>* q = &b;
q->func1(3);
}
et la sortie est:
A:A<string> a
A<--B:B<string> c
A<--B:3
Et plus tard, j'ai ajouté une nouvelle classe X:
class X
{
public:
template <typename T>
virtual void func2(const T& p)
{
cout<<"C:"<<p<<endl;
}
};
Quand j'ai essayé d'utiliser la classe X dans main () comme ceci:
X x;
x.func2<string>("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&)'
Il est donc évident que:
Non, ils ne peuvent pas. Mais:
template<typename T>
class Foo {
public:
template<typename P>
void f(const P& p) {
((T*)this)->f<P>(p);
}
};
class Bar : public Foo<Bar> {
public:
template<typename P>
void f(const P& p) {
std::cout << p << std::endl;
}
};
int main() {
Bar bar;
Bar *pbar = &bar;
pbar -> f(1);
Foo<Bar> *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.
Foo
pointeur est qualifié de Foo<Bar>
, il ne peut pas pointer vers un Foo<Barf>
ou Foo<XXX>
.
Non, les fonctions membres du modèle ne peuvent pas être virtuelles.
Dans les autres réponses, la fonction de modèle proposée est une façade et n'offre aucun avantage pratique.
Le langage ne permet 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 cependant nécessaire de définir pour chaque combinaison de type de modèle une fonction de wrapper virtuel factice:
#include <memory>
#include <iostream>
#include <iomanip>
//---------------------------------------------
// 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 <typename T>
void getAreaT(T &area) {
area = static_cast<T>(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 <typename T>
void getAreaT(T &area) {
area = static_cast<T>(radius * radius * 3.1415926535897932385L);
}
};
//---------------------------------------------
// Main
int main()
{
// get area of square using template based function T=float
std::unique_ptr<Geometry> geometry = std::make_unique<Square>();
float areaSquare;
geometry->getArea(areaSquare);
// get area of circle using template based function T=long double
geometry = std::make_unique<Circle>();
long double areaCircle;
geometry->getArea(areaCircle);
std::cout << std::setprecision(20) << "Square area is " << areaSquare << ", Circle area is " << areaCircle << std::endl;
return 0;
}
Production:
La zone carrée est de 1, la zone du cercle est de 3,1415926535897932385
Essayez-le ici
Pour répondre à la deuxième partie de la question:
S'ils peuvent être virtuels, quel est un exemple de scénario dans lequel on utiliserait une telle fonction?
Ce n'est pas une chose déraisonnable de vouloir faire. Par exemple, Java (où chaque méthode est virtuelle) n'a aucun problème avec les méthodes génériques.
Un exemple en C ++ de vouloir un modèle de fonction virtuelle est une fonction membre qui accepte un itérateur générique. Ou une fonction membre qui accepte un objet fonction générique.
La solution à ce problème est d'utiliser l'effacement de type avec boost :: any_range et boost :: function, qui vous permettra d'accepter un itérateur ou un foncteur générique sans avoir besoin de 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 (l'une d'entre elles 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 du modèle TMethod
dans les implémentations dérivées ( AImpl
, BImpl
) et d'utiliser Derived<*Impl>
.
class Base
{
public:
virtual ~Base()
{
}
template <typename T>
T Method(T t)
{
return VMethod(t);
}
private:
virtual int VMethod(int t) = 0;
virtual double VMethod(double t) = 0;
};
template <class Impl>
class Derived : public Impl
{
public:
template <class... TArgs>
Derived(TArgs&&... args)
: Impl(std::forward<TArgs>(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 <typename T>
T TMethod(T t)
{
return t - i;
}
private:
int i;
};
using A = Derived<AImpl>;
class BImpl : public Base
{
protected:
BImpl(int p)
: i(p)
{
}
template <typename T>
T TMethod(T t)
{
return t + i;
}
private:
int i;
};
using B = Derived<BImpl>;
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;
}
Production:
0
1
2
3
NB:
Base::Method
est en fait excédentaire pour le code réel ( VMethod
peut être rendu public et utilisé directement). Je l'ai ajouté pour qu'il ressemble à une méthode de modèle «virtuelle» réelle.
Base
classe d' origine chaque fois que vous devez appeler une fonction de modèle avec un type d'argument non compatible avec ceux mis en œuvre jusqu'à présent. Éviter cette nécessité est l'intention des modèles ...
Bien qu'une question plus ancienne à laquelle beaucoup ont répondu, je pense qu'une méthode succincte, pas si différente des autres publiées, consiste à utiliser une macro mineure pour faciliter la duplication des déclarations de classe.
// abstract.h
// Simply define the types that each concrete class will use
#define IMPL_RENDER() \
void render(int a, char *b) override { render_internal<char>(a, b); } \
void render(int a, short *b) override { render_internal<short>(a, b); } \
// ...
class Renderable
{
public:
// Then, once for each on the abstract
virtual void render(int a, char *a) = 0;
virtual void render(int a, short *b) = 0;
// ...
};
Alors maintenant, pour implémenter notre sous-classe:
class Box : public Renderable
{
public:
IMPL_RENDER() // Builds the functions we want
private:
template<typename T>
void render_internal(int a, T *b); // One spot for our logic
};
L'avantage ici est que, lors de l'ajout d'un type nouvellement pris en charge, tout peut être fait à partir de l'en-tête abstrait et renoncer éventuellement à le rectifier dans plusieurs fichiers source / en-tête.
Au moins avec gcc 5.4, les fonctions virtuelles pourraient être des membres de modèle mais doivent être des modèles eux-mêmes.
#include <iostream>
#include <string>
class first {
protected:
virtual std::string a1() { return "a1"; }
virtual std::string mixt() { return a1(); }
};
class last {
protected:
virtual std::string a2() { return "a2"; }
};
template<class T> class mix: first , T {
public:
virtual std::string mixt() override;
};
template<class T> std::string mix<T>::mixt() {
return a1()+" before "+T::a2();
}
class mix2: public mix<last> {
virtual std::string a1() override { return "mix"; }
};
int main() {
std::cout << mix2().mixt();
return 0;
}
Les sorties
mix before a2
Process finished with exit code 0
Essaye ça:
Écrivez dans classeder.h:
template <typename T>
class Example{
public:
T c_value;
Example(){}
T Set(T variable)
{
return variable;
}
virtual Example VirtualFunc(Example paraM)
{
return paraM.Set(c_value);
}
Vérifiez, si vous travaillez avec cela, d'écrire ce code dans main.cpp:
#include <iostream>
#include <classeder.h>
int main()
{
Example exmpl;
exmpl.c_value = "Hello, world!";
std::cout << exmpl.VirtualFunc(exmpl);
return 0;
}