Un modello di funzione di un membro della classe può essere virtuale?


304

Ho sentito che i modelli di funzione dei membri della classe C ++ non possono essere virtuali. È vero?

Se possono essere virtuali, qual è un esempio di uno scenario in cui si userebbe una tale funzione?


12
Ho affrontato un problema simile e ho anche appreso che è controverso essere virtuali e template allo stesso tempo. La mia soluzione era quella di scrivere il modello magico che sarà comune tra le classi derivate e chiamare una funzione virtuale pura che fa la parte specializzata. Questo è ovviamente legato alla natura del mio problema, quindi potrebbe non funzionare in tutti i casi.
Tamás Szelei,

Risposte:


329

I modelli riguardano tutto il codice di generazione del compilatore in fase di compilazione . Le funzioni virtuali riguardano il sistema di runtime per capire quale funzione chiamare in fase di runtime .

Una volta che il sistema di runtime ha capito che avrebbe bisogno di chiamare una funzione virtuale modellata, la compilazione è fatta e il compilatore non può più generare l'istanza appropriata. Pertanto non è possibile disporre di modelli di funzioni dei membri virtuali.

Tuttavia, ci sono alcune tecniche potenti e interessanti derivanti dalla combinazione di polimorfismo e modelli, in particolare la cosiddetta cancellazione del tipo .


32
Non vedo un motivo linguistico per questo, solo motivi di implementazione . vtables non fa parte del linguaggio - solo il modo standard in cui i compilatori implementano il linguaggio.
Gerardw,

16
Virtual functions are all about the run-time system figuring out which function to call at run-time- scusa ma questo è un modo piuttosto sbagliato e abbastanza confuso. È solo un riferimento indiretto e non è coinvolto alcun "runtime capimento", durante la compilazione è noto che la funzione da chiamare è quella indicata dal puntatore n-esimo nella vtable. "Capire" implica che ci sono controlli del tipo e simili, il che non è il caso. Once the run-time system figured out it would need to call a templatized virtual function- se la funzione è virtuale è noto al momento della compilazione.
dtech,

9
@ddriver: 1. Se i compilatori vedono void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }, allora "sa" quale funzione viene invocata nel punto cb.f()chiamato e non lo sa per vb.f(). Quest'ultimo deve essere scoperto in fase di esecuzione , dal sistema di runtime . Sia che tu voglia chiamare questo "capire" e che sia più o meno efficiente, non cambia un po 'questi fatti.
sabato

9
@ddriver: 2. Le istanze dei modelli di funzione (membro) sono funzioni (membro), quindi non c'è alcun problema nel mettere un puntatore a tale istanza nella vtable. Ma quali istanze del modello sono necessarie sono note solo quando viene compilato il chiamante, mentre i vtables vengono impostati quando vengono compilate la classe base e le classi derivate. E questi sono tutti compilati separatamente. Ancora peggio: le nuove classi derivate possono essere collegate ai sistemi in esecuzione in fase di esecuzione (si pensi che il browser carichi un plug-in in modo dinamico). Anche il codice sorgente del chiamante potrebbe essere perso a lungo quando viene creata una nuova classe derivata.
sabato

9
@sbi: Perché stai facendo ipotesi basate sul mio nome? Non ho confuso generici e modelli. So che i generici di Java sono puramente runtime. Non hai spiegato in modo esaustivo perché non puoi avere modelli di funzione membro virtuale in C ++, ma InQsitive l'ha fatto. Hai semplificato eccessivamente il modello e la meccanica virtuale in "tempo di compilazione" rispetto a "tempo di esecuzione" e hai concluso che "non puoi avere modelli di funzione membro virtuale". Ho fatto riferimento alla risposta di InQsitive, che fa riferimento a "Modelli C ++ La guida completa". Non lo considero "agitando la mano". Buona giornata.
Javanator,

133

Dai modelli C ++ La guida completa:

I modelli di funzioni membro non possono essere dichiarati virtuali. Questo vincolo è imposto perché la normale implementazione del meccanismo di chiamata della funzione virtuale utilizza una tabella di dimensioni fisse con una voce per funzione virtuale. Tuttavia, il numero di istanze di un modello di funzione membro non è fisso fino a quando l'intero programma non è stato tradotto. Pertanto, il supporto dei modelli di funzioni dei membri virtuali richiederebbe il supporto di un nuovo tipo di meccanismo nei compilatori e nei linker C ++. Al contrario, i membri ordinari dei modelli di classe possono essere virtuali perché il loro numero viene fissato quando viene creata un'istanza di una classe


8
Penso che il compilatore e i linker C ++ di oggi, in particolare con il supporto per l'ottimizzazione del tempo di collegamento, dovrebbero essere in grado di generare i vtables e gli offset richiesti al momento del collegamento. Quindi forse avremo questa funzionalità in C ++ 2b?
Kai Petzke,

33

Al momento C ++ non consente le funzioni dei membri del modello virtuale. Il motivo più probabile è la complessità della sua attuazione. Rajendra fornisce una buona ragione per cui non può essere fatto in questo momento, ma potrebbe essere possibile con ragionevoli modifiche allo standard. Soprattutto capire quante istanze di una funzione templata esistono effettivamente e costruire la vtable sembra difficile se si considera il posto della chiamata di funzione virtuale. Le persone standard hanno solo molte altre cose da fare in questo momento e C ++ 1x è molto lavoro anche per gli autori del compilatore.

Quando avresti bisogno di una funzione membro basata su modelli? Una volta mi sono imbattuto in una situazione del genere in cui ho cercato di riformattare una gerarchia con una pura classe di base virtuale. Era uno stile scadente per l'implementazione di strategie diverse. Volevo cambiare l'argomento di una delle funzioni virtuali in un tipo numerico e invece di sovraccaricare la funzione membro e sovrascrivere ogni sovraccarico in tutte le sottoclassi ho provato ad usare le funzioni del modello virtuale (e ho dovuto scoprire che non esistono .)


5
@pmr: una funzione virtuale potrebbe essere chiamata da un codice che non esisteva nemmeno al momento della compilazione della funzione. In che modo il compilatore determinerebbe quali istanze di una funzione (teorica) del membro modello virtuale generare per il codice che non esiste nemmeno?
sbi

2
@sbi: Sì, la compilazione separata sarebbe un grosso problema. Non sono affatto esperto di compilatori C ++, quindi non posso offrire una soluzione. Come per le funzioni basate su modelli in generale, dovrebbe essere nuovamente istanziato in ogni unità di compilazione, giusto? Non risolverebbe questo problema?
pm

2
@sbi se ti riferisci al caricamento dinamico delle librerie, questo è un problema generale con le classi / funzioni dei template, non solo con i metodi dei template virtuali.
Oak,

"C ++ non consente [...]" - apprezzerebbe vedere il riferimento allo standard (non importa se quello aggiornato quando è stata scritta la risposta o quello aggiornato otto anni dopo) ...
Aconcagua

19

Tabelle delle funzioni virtuali

Cominciamo con alcuni retroscena sulle tabelle delle funzioni virtuali e su come funzionano ( fonte ):

[20.3] Qual è la differenza tra come vengono chiamate le funzioni membro virtuale e non virtuale?

Le funzioni membro non virtuali vengono risolte staticamente. Cioè, la funzione membro viene selezionata staticamente (in fase di compilazione) in base al tipo di puntatore (o riferimento) all'oggetto.

Al contrario, le funzioni dei membri virtuali vengono risolte dinamicamente (in fase di esecuzione). Ossia, la funzione membro viene selezionata in modo dinamico (in fase di esecuzione) in base al tipo di oggetto, non al tipo di puntatore / riferimento a quell'oggetto. Questo si chiama "associazione dinamica". La maggior parte dei compilatori utilizza una variante della seguente tecnica: se l'oggetto ha una o più funzioni virtuali, il compilatore inserisce un puntatore nascosto nell'oggetto chiamato "puntatore virtuale" o "puntatore v". Questo puntatore a V punta a una tabella globale chiamata "tabella virtuale" o "tabella v".

Il compilatore crea una v-table per ogni classe che ha almeno una funzione virtuale. Ad esempio, se la classe Circle ha funzioni virtuali per draw () e move () e resize (), ci sarebbe esattamente una v-table associata alla classe Circle, anche se ci fossero oggetti Circle gazillion e il puntatore v di ciascuno di quegli oggetti Circle puntava alla v-table Circle. La stessa v-table ha puntatori a ciascuna delle funzioni virtuali nella classe. Ad esempio, la v-table Circle avrebbe tre puntatori: un puntatore a Circle :: draw (), un puntatore a Circle :: move () e un puntatore a Circle :: resize ().

Durante l'invio di una funzione virtuale, il sistema di runtime segue il v-pointer dell'oggetto sulla v-table della classe, quindi segue lo slot appropriato nella v-table per il codice del metodo.

Il sovraccarico di spazio della tecnica sopra è nominale: un puntatore extra per oggetto (ma solo per gli oggetti che dovranno eseguire l'associazione dinamica), più un puntatore extra per metodo (ma solo per i metodi virtuali). Anche il sovraccarico di tempo è abbastanza nominale: rispetto a una normale chiamata di funzione, una chiamata di funzione virtuale richiede due ulteriori recuperi (uno per ottenere il valore del puntatore a V, un secondo per ottenere l'indirizzo del metodo). Nessuna di questa attività di runtime si verifica con funzioni non virtuali, poiché il compilatore risolve funzioni non virtuali esclusivamente in fase di compilazione in base al tipo di puntatore.


Il mio problema o come sono venuto qui

Sto tentando di utilizzare qualcosa del genere ora per una classe base di file cubo con funzioni di caricamento ottimizzate basate su modelli che verranno implementate in modo diverso per diversi tipi di cubi (alcuni memorizzati per pixel, altri per immagine, ecc.).

Qualche codice:

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;

Cosa mi piacerebbe che fosse, ma non verrà compilato a causa di una combinazione di modelli virtuali:

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;

Ho finito per spostare la dichiarazione del modello a livello di classe . Questa soluzione avrebbe costretto i programmi a conoscere specifici tipi di dati che avrebbero letto prima di leggerli, il che è inaccettabile.

Soluzione

attenzione, questo non è molto carino ma mi ha permesso di rimuovere il codice di esecuzione ripetitiva

1) nella classe 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) e nelle classi secondarie

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

Si noti che LoadAnyCube non è dichiarato nella classe base.


Ecco un'altra risposta di overflow dello stack con una soluzione : hai bisogno di una soluzione alternativa per i membri del modello virtuale .


1
Ho incontrato la stessa situazione e la struttura ereditaria delle classi di massa. le macro hanno aiutato.
ZFY

16

Il seguente codice può essere compilato ed eseguito correttamente, usando MinGW G ++ 3.4.5 su 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);
}

e l'output è:

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

E più tardi ho aggiunto una nuova classe X:

class X
{
public:
    template <typename T>
    virtual void func2(const T& p)
    {
        cout<<"C:"<<p<<endl;
    }
};

Quando ho provato ad usare la classe X in main () in questo modo:

X x;
x.func2<string>("X x");

g ++ segnala il seguente errore:

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

Quindi è ovvio che:

  • la funzione membro virtuale può essere utilizzata in un modello di classe. È facile per il compilatore costruire vtable
  • È impossibile definire una funzione membro del modello di classe come virtuale, come puoi vedere, è difficile determinare la firma della funzione e allocare le voci vtable.

19
Un modello di classe può avere funzioni di membro virtuale. Una funzione membro potrebbe non essere sia un modello di funzione membro sia una funzione membro virtuale.
James McNellis,

1
in realtà fallisce con gcc 4.4.3. Sul mio sistema sicuramente Ubuntu 10.04
blueskin il

3
Questo è totalmente diverso da quello che la domanda ha posto. Qui l'intera classe base è modellata. Ho compilato questo genere di cose prima. Questo sarebbe compilabile anche su Visual Studio 2010
ds-bos-msk,

14

No non possono. Ma:

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

ha lo stesso effetto se tutto ciò che vuoi fare è avere un'interfaccia comune e rimandare l'implementazione alle sottoclassi.


3
Questo è noto come CRTP se qualcuno è curioso.
Michael Choi,

1
Ma questo non aiuta nei casi in cui si ha una gerarchia di classi e si vuole poter chiamare metodi virtuali di puntatori alle classi di base. Il Foopuntatore è qualificato come Foo<Bar>, non può puntare a Foo<Barf>o Foo<XXX>.
Kai Petzke,

@KaiPetzke: non puoi costruire un puntatore senza vincoli, no. Ma puoi modellare qualsiasi codice che non ha bisogno di conoscere il tipo concreto, che ha più o meno lo stesso effetto (almeno concettualmente - implementazione ovviamente completamente diversa).
Tom

8

No, le funzioni dei membri del modello non possono essere virtuali.


9
La mia curiosità è: perché? Quali problemi deve affrontare il compilatore?
WannaBeGeek

1
È necessaria una dichiarazione nell'ambito (almeno, al fine di ottenere i tipi corretti). Lo standard (e la lingua) richiede una dichiarazione nell'ambito per gli identificatori utilizzati.
Dirkgently

4

Nelle altre risposte la funzione modello proposta è una facciata e non offre alcun vantaggio pratico.

  • Le funzioni modello sono utili per scrivere codice una sola volta usando tipi diversi.
  • Le funzioni virtuali sono utili per avere un'interfaccia comune per classi diverse.

Il linguaggio non consente le funzioni del modello virtuale ma con una soluzione alternativa è possibile avere entrambe, ad esempio un'implementazione del modello per ogni classe e un'interfaccia virtuale comune.

È tuttavia necessario definire per ciascuna combinazione di tipi di modello una funzione fittizia di wrapper virtuale:

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

Produzione:

L'area quadrata è 1, l'area del cerchio è 3.1415926535897932385

Provalo qui


3

Per rispondere alla seconda parte della domanda:

Se possono essere virtuali, qual è un esempio di uno scenario in cui si userebbe una tale funzione?

Questa non è una cosa irragionevole da voler fare. Ad esempio, Java (dove ogni metodo è virtuale) non ha problemi con i metodi generici.

Un esempio in C ++ di voler un modello di funzione virtuale è una funzione membro che accetta un iteratore generico. O una funzione membro che accetta un oggetto funzione generico.

La soluzione a questo problema è utilizzare la cancellazione del tipo con boost :: any_range e boost :: function, che ti permetterà di accettare un iteratore o un funzione generico senza la necessità di rendere la tua funzione un modello.


6
I generici Java sono zucchero sintattico per il casting. Non sono gli stessi dei modelli.
Brice M. Dempsey,

2
@ BriceM.Dempsey: Potresti dire che il casting è il modo in cui Java implementa i generici, piuttosto che il contrario ... e semestralmente, l'Exlipy del caso d'uso è IMO valido.
einpoklum,

2

Esiste una soluzione alternativa per il "metodo modello virtuale" se è noto in anticipo un set di tipi per il metodo modello.

Per mostrare l'idea, nell'esempio che segue vengono utilizzati solo due tipi ( inte double).

Lì, un metodo modello "virtuale" ( Base::Method) chiama il metodo virtuale corrispondente (uno dei Base::VMethod) che, a sua volta, chiama l'implementazione del metodo modello ( Impl::TMethod).

È sufficiente implementare il metodo modello TMethodnelle implementazioni derivate ( AImpl, BImpl) e nell'uso 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;
}

Produzione:

0
1
2
3

NB: in Base::Methodrealtà è un surplus per il codice reale ( VMethodpuò essere reso pubblico e utilizzato direttamente). L'ho aggiunto in modo che sembri un vero e proprio metodo di modello "virtuale".


Ho trovato questa soluzione mentre risolvo un problema sul lavoro. Sembra simile a quello di Mark Essel sopra, ma, spero, è meglio implementato e spiegato.
sad1raf

Lo classificherei già come offuscamento del codice, e ancora non ti aggiri dal fatto che devi modificare la Baseclasse originale ogni volta che devi chiamare una funzione template con un tipo di argomento non compatibile con quelli implementati finora. Evitare questa necessità è l'intenzione dei modelli ...
Aconcagua

L'approccio di Essels è totalmente diverso: funzioni virtuali ordinarie che accettano diverse istanze di modelli - e la funzione di modello finale nella classe derivata serve solo ad evitare la duplicazione del codice e non ha nemmeno una controparte nella classe base ...
Aconcagua

2

Mentre una domanda più vecchia a cui è stato risposto da molti credo che un metodo sintetico, non così diverso dagli altri pubblicati, sia quello di utilizzare una macro minore per facilitare la duplicazione delle dichiarazioni di 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;
    // ...
};

Quindi ora, per implementare la nostra sottoclasse:

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

Il vantaggio qui è che, quando si aggiunge un nuovo tipo supportato, tutto può essere fatto dall'intestazione astratta e rinunciare eventualmente a correggerlo in più file sorgente / intestazione.


0

Almeno con gcc 5.4 le funzioni virtuali potrebbero essere membri del template ma devono essere essi stessi template.

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

Uscite

mix before a2
Process finished with exit code 0

0

Prova questo:

Scrivi in ​​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);
    }

Controlla, se lavori con questo, per scrivere questo codice in main.cpp:

#include <iostream>
#include <classeder.h>

int main()
{
     Example exmpl;
     exmpl.c_value = "Hello, world!";
     std::cout << exmpl.VirtualFunc(exmpl);
     return 0;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.