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

304

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?

WannaBeGeek
la source
12
J'ai fait face à un problème similaire et j'ai également appris qu'il est controversé d'être virtuel et modèle en même temps. Ma solution a été d'écrire la magie du modèle qui sera commune aux classes dérivées et d'appeler une fonction virtuelle pure qui fait la partie spécialisée. Ceci est bien sûr lié à la nature de mon problème, donc peut ne pas fonctionner dans tous les cas.
Tamás Szelei

Réponses:

329

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 .

sbi
la source
32
Je ne vois pas de raison linguistique pour cela, seulement des raisons de mise en œuvre . vtables ne fait pas partie du langage - juste la manière standard dont les compilateurs implémentent le langage.
gerardw
16
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.
dtech
9
@ddriver: 1. Si les compilateurs voient 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.
sbi
9
@ddriver: 2. Les instances de modèles de fonctions (membres) sont des fonctions (membres), donc il n'y a aucun problème à mettre un pointeur vers une telle instance dans la table virtuelle. Mais les instances de modèle nécessaires ne sont connues que lorsque l'appelant est compilé, tandis que les vtables sont configurées lorsque la classe de base et les classes dérivées sont compilées. Et ceux-ci sont tous compilés séparément. Pire encore: de nouvelles classes dérivées peuvent être liées aux systèmes en cours d'exécution lors de l'exécution (pensez à votre navigateur qui charge un plugin de manière dynamique). Même le code source de l'appelant peut être perdu longtemps lors de la création d'une nouvelle classe dérivée.
sbi
9
@sbi: Pourquoi faites-vous des suppositions basées sur mon nom? Je n'ai pas confondu génériques et modèles. Je sais que les génériques Java sont purement exécutables. Vous n'avez pas expliqué de manière exhaustive pourquoi vous ne pouvez pas avoir de modèles de fonction de membre virtuel en C ++, mais InQsitive l'a fait. Vous avez trop simplifié le modèle et la mécanique virtuelle pour «compiler le temps» vs «exécuter» et conclu que «vous ne pouvez pas avoir de modèles de fonction de membre virtuel». J'ai fait référence à la réponse d'InQsitive, qui fait référence à "Templates C ++ The Complete Guide". Je ne considère pas que cela "agite la main". Bonne journée.
Javanator
133

À 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

InQusitive
la source
8
Je pense que le compilateur et les éditeurs de liens C ++ d'aujourd'hui, notamment avec la prise en charge de l'optimisation de la durée de liaison, devraient être en mesure de générer les vtables et décalages requis au moment de la liaison. Alors peut-être que nous aurons cette fonctionnalité en C ++ 2b?
Kai Petzke
33

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

pmr
la source
5
@pmr: Une fonction virtuelle peut être appelée à partir d'un code qui n'existait même pas lors de la compilation de la fonction. Comment le compilateur déterminerait-il les instances d'une fonction membre de modèle virtuel (théorique) à générer pour du code qui n'existe même pas?
sbi
2
@sbi: Oui, une compilation séparée serait un énorme problème. Je ne suis pas du tout expert des compilateurs C ++ donc je ne peux pas proposer de solution. Comme avec les modèles de fonctions en général, il devrait être instancié à nouveau dans chaque unité de compilation, non? Cela ne résoudrait-il pas le problème?
pmr
2
@sbi si vous faites référence au chargement dynamique de bibliothèques, c'est un problème général avec les classes / fonctions de modèle, pas seulement avec les méthodes de modèle virtuel.
Oak
"C ++ ne permet pas [...]" - apprécierait de voir une référence à la norme (que ce soit à jour lorsque la réponse a été écrite ou à jour huit ans plus tard) ...
Aconcagua
19

Tables de fonctions virtuelles

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.


Mon problème ou comment je suis venu ici

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.

Solution

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 .

Mark Essel
la source
1
J'ai rencontré la même situation, et la structure d'héritage des classes de masse. les macros ont aidé.
ZFY
16

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:

  • 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 comme virtuelle, comme vous pouvez le voir, il est difficile de déterminer la signature de la fonction et d'allouer les entrées vtable.
Brent81
la source
19
Un modèle de classe peut avoir des fonctions membres virtuelles. Une fonction membre ne peut pas être à la fois un modèle de fonction membre et une fonction membre virtuelle.
James McNellis
1
il échoue en fait avec gcc 4.4.3. Sur mon système à coup sûr Ubuntu 10.04
blueskin
3
C'est totalement différent de ce que la question a posé. Ici, la classe de base entière est basée sur des modèles. J'ai déjà compilé ce genre de choses. Cela se compilerait également sur Visual Studio 2010
ds-bos-msk
14

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.

À M
la source
3
Ceci est connu comme CRTP si quelqu'un est curieux.
Michael Choi
1
Mais cela n'aide pas dans les cas où l'on a une hiérarchie de classes et veut pouvoir appeler des méthodes virtuelles de pointeurs vers les classes de base. Votre Foopointeur est qualifié de Foo<Bar>, il ne peut pas pointer vers un Foo<Barf>ou Foo<XXX>.
Kai Petzke
@KaiPetzke: Vous ne pouvez pas construire un pointeur sans contrainte, non. Mais vous pouvez modeler n'importe quel code qui n'a pas besoin de connaître le type concret, ce qui a à peu près le même effet (conceptuellement au moins - implémentation évidemment complètement différente).
Tom
8

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

dirkgently
la source
9
Ma curiosité est: pourquoi? À quels problèmes le compilateur est-il confronté pour ce faire?
WannaBeGeek
1
Vous avez besoin d'une déclaration de portée (au moins, afin d'obtenir les types corrects). Il est requis par la norme (et la langue) d'avoir une déclaration dans la portée des identifiants que vous utilisez.
Dirkgently
4

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

andreaplanet
la source
3

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.

exclipy
la source
6
Les génériques Java sont du sucre syntaxique pour le casting. Ce ne sont pas les mêmes que les modèles.
Brice M. Dempsey
2
@ BriceM.Dempsey: On pourrait dire que le casting est la façon dont Java implémente les génériques, plutôt que l'inverse ... et sémantiquement, le cas d'utilisation exclipy présenté est valide IMO.
einpoklum
2

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 ( intet 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 TMethoddans 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::Methodest en fait excédentaire pour le code réel ( VMethodpeut être rendu public et utilisé directement). Je l'ai ajouté pour qu'il ressemble à une méthode de modèle «virtuelle» réelle.

sad1raf
la source
J'ai trouvé cette solution tout en résolvant un problème au travail. Il semble similaire à celui de Mark Essel ci-dessus, mais j'espère qu'il est mieux mis en œuvre et expliqué.
sad1raf
Je qualifierais déjà cela d'obscurcissement de code, et vous ne contournez toujours pas le fait que vous devez modifier la Baseclasse 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 ...
Aconcagua
L'approche d'Essels est totalement différente: les fonctions virtuelles ordinaires acceptant différents instanciations de modèle - et la fonction de modèle finale dans la classe dérivée ne sert qu'à éviter la duplication de code et n'a même pas de contrepartie dans la classe de base ...
Aconcagua
2

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.

mccatnm
la source
0

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
Maxim Sinev
la source
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;
}
GobeRadJem32
la source