2010-03-01 3 views
220

Ho sentito dire che i modelli di funzioni dei membri della classe C++ non possono essere virtuali. È vero?Un modello di funzione membro della classe C++ può essere virtuale?

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

+7

Ho affrontato un problema simile e ho anche appreso che è controverso essere virtuale e modello allo stesso tempo. La mia soluzione era 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 ogni caso. –

risposta

1

No, le funzioni membro modello non possono essere virtuali.

+8

La mia curiosità è: perché? Quali problemi si trova ad affrontare il compilatore? – WannaBeGeek

+1

Hai bisogno di una dichiarazione in ambito (almeno, al fine di ottenere i tipi corretti). È richiesto dallo standard (e dalla lingua) di avere una dichiarazione nell'ambito per gli identificatori che si utilizzano. – dirkgently

261

I modelli sono tutti relativi al codice di generazione del compilatore in in fase di compilazione. Le funzioni virtuali riguardano esclusivamente il sistema runtime che identifica la funzione da chiamare al run-time.

Una volta che il sistema di run-time ha capito che avrebbe dovuto chiamare una funzione virtuale temporizzata, la compilazione è stata completata e il compilatore non può generare più l'istanza appropriata. Pertanto non è possibile avere modelli di funzioni membro virtuali.

Tuttavia, ci sono alcune tecniche potenti e interessanti derivanti dalla combinazione di polimorfismo e modelli, in particolare il cosiddetto type erasure.

+2

Appena imbattuto in questo e apprezzare mag e le vostre risposte. Sto cercando di non avere le mie speranze troppo alte, ma il tipo di cancellazione potrebbe essere quello che stavo cercando. –

+15

Non vedo un motivo _language_ per questo, solo ragioni _implementazione_. i vtables non fanno parte della lingua - solo i compilatori standard usano la lingua. – gerardw

+10

'Le funzioni virtuali riguardano tutto il sistema run-time che individua la funzione da chiamare in fase di esecuzione' - scusate ma questo è un modo piuttosto sbagliato e piuttosto confuso. È solo indiretta, e non vi è alcun "runtime di rilevamento" coinvolto, è noto durante la compilazione che la funzione da chiamare è quella puntata dal n-esimo puntatore nel vtable. "Capire" implica che ci sono controlli di tipo e così via, che non è il caso. 'Una volta che il sistema run-time ha capito che avrebbe dovuto chiamare una funzione virtuale temporizzata - se la funzione è virtuale o meno è conosciuta in fase di compilazione. – dtech

26

C++ non consente le funzioni di membro del modello virtuale al momento. La ragione più probabile è la complessità della sua implementazione. Rajendra dà una buona ragione per cui non può essere fatto in questo momento, ma potrebbe essere possibile con cambiamenti ragionevoli dello standard. Soprattutto si sta lavorando sul numero di istanze di una funzione basata su modelli effettivamente esistenti e la creazione del vtable sembra difficile se si considera il posto della chiamata alla funzione virtuale. Le persone degli standard hanno solo un sacco di cose da fare in questo momento e C++ 1x è un sacco di lavoro anche per gli scrittori di compilatori.

Quando è necessaria una funzione membro basata su modello? Una volta mi sono imbattuto in una situazione del genere in cui ho cercato di ridefinire una gerarchia con una pura classe di base virtuale. Era uno stile povero per implementare diverse strategie. 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 a utilizzare le funzioni del modello virtuale (e ho dovuto scoprire che non esistono .)

+4

@pmr: una funzione virtuale potrebbe essere chiamata da un codice che non esisteva nemmeno quando la funzione è stata compilata. In che modo il compilatore può determinare quali istanze di una funzione membro virtuale (teorico) del modello generare per codice che non esiste nemmeno? – sbi

+2

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

+2

@sbi se ti stai riferendo a caricare dinamicamente le librerie, questo è un problema generale con le classi/funzioni template, non solo con i metodi di template virtuali. – Oak

12

il seguente codice può essere compilato e funziona 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'uscita è:

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

e poi ho aggiunto un nuova classe X:

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

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

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

g ++ segnalare 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. Per il compilatore è facile costruire vtable
  • È impossibile definire una funzione membro del modello di classe come virtuale, come è possibile vedere, è difficile determinare la firma della funzione e allocare le voci vtable.
+16

Un modello di classe può avere funzioni membro virtuali. Una funzione membro potrebbe non essere sia un modello di funzione membro che una funzione membro virtuale. –

+1

in realtà non funziona con gcc 4.4.3. Sul mio sistema di sicuro Ubuntu 10.04 – blueskin

+2

Questo è totalmente diverso da quello che la domanda posta.Qui l'intera classe base è basata su modelli. Ho compilato questo genere di cose prima. Questo si compilerebbe anche su Visual Studio 2010 – bigD

3

Per rispondere alla seconda parte della domanda:

If they can be virtual, what is an example of a scenario in which one would use such a function?

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

Un esempio in C++ di volere 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 consiste nell'utilizzare la cancellazione dei tipi con boost :: any_range e boost :: function, che consente di accettare un iteratore o un functor generico senza la necessità di rendere la funzione un modello.

+5

I generici Java sono zucchero sintattico per il casting. Non sono uguali a modelli. –

+2

@ BriceM.Dempsey: Si potrebbe dire che il casting è il modo in cui Java implementa i farmaci generici, piuttosto che il contrario ... e semicalmente, l'uso di Exclipy case-use è IMO valido. – einpoklum

16

Tabelle Virtuali funzione

Cominciamo con alcuni retroscena sui tavoli funzione virtuale e come funzionano (source):

[20.3] What's the difference between how virtual and non-virtual member functions are called?

Non-virtual member functions are resolved statically. That is, the member function is selected statically (at compile-time) based on the type of the pointer (or reference) to the object.

In contrast, virtual member functions are resolved dynamically (at run-time). That is, the member function is selected dynamically (at run-time) based on the type of the object, not the type of the pointer/reference to that object. This is called "dynamic binding." Most compilers use some variant of the following technique: if the object has one or more virtual functions, the compiler puts a hidden pointer in the object called a "virtual-pointer" or "v-pointer." This v-pointer points to a global table called the "virtual-table" or "v-table."

The compiler creates a v-table for each class that has at least one virtual function. For example, if class Circle has virtual functions for draw() and move() and resize(), there would be exactly one v-table associated with class Circle, even if there were a gazillion Circle objects, and the v-pointer of each of those Circle objects would point to the Circle v-table. The v-table itself has pointers to each of the virtual functions in the class. For example, the Circle v-table would have three pointers: a pointer to Circle::draw(), a pointer to Circle::move(), and a pointer to Circle::resize().

During a dispatch of a virtual function, the run-time system follows the object's v-pointer to the class's v-table, then follows the appropriate slot in the v-table to the method code.

The space-cost overhead of the above technique is nominal: an extra pointer per object (but only for objects that will need to do dynamic binding), plus an extra pointer per method (but only for virtual methods). The time-cost overhead is also fairly nominal: compared to a normal function call, a virtual function call requires two extra fetches (one to get the value of the v-pointer, a second to get the address of the method). None of this runtime activity happens with non-virtual functions, since the compiler resolves non-virtual functions exclusively at compile-time based on the type of the pointer.


il mio problema, o come sono arrivato qui

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

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

Quello che mi piacerebbe che fosse, ma non si compila a causa di una combo su 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 il modello dichiarazione al 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

avvertimento, questo non è molto bella, ma mi ha permesso di rimuovere l'esecuzione di codice ripetitivo

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 nel classi per bambini

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

N Nota che LoadAnyCube non è dichiarato nella classe base.


Ecco un'altra risposta overflow dello stack con un lavoro intorno: need a virtual template member workaround.

71

Da C++ Templates La guida completa:

Member function templates cannot be declared virtual. This constraint is imposed because the usual implementation of the virtual function call mechanism uses a fixed-size table with one entry per virtual function. However, the number of instantiations of a member function template is not fixed until the entire program has been translated. Hence, supporting virtual member function templates would require support for a whole new kind of mechanism in C++ compilers and linkers. In contrast, the ordinary members of class templates can be virtual because their number is fixed when a class is instantiated

6

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 più o meno lo stesso effetto se tutto quello che vogliamo fare è avere un'interfaccia comune e rinviare l'attuazione di sottoclassi.

0

Almeno con gcc 5.4 le funzioni virtuali possono essere membri del modello, ma devono essere modelli stessi.

#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

C'è una soluzione per il 'metodo template virtuale' se insieme di tipi per il metodo modello è noto in anticipo.

Per mostrare l'idea, nell'esempio seguente vengono utilizzati solo due tipi (int e double).

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

È necessario solo implementare il metodo modello TMethod nelle implementazioni derivate (AImpl, BImpl) e utilizzare 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; 
} 

uscita:

0 
1 
2 
3 

NB: Base::Method è in realtà surplus codice reale (VMethod possono essere resi pubblici e utilizzati direttamente). L'ho aggiunto in modo che assomigli a un metodo di modello "virtuale" effettivo.

+0

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

+0

Lo classificherei già come offuscazione del codice, e ancora non si aggira il fatto che si deve modificare la classe 'Base' originale ogni volta che è necessario chiamare una funzione template con un tipo di argomento non compatibile con quelli implementato finora. Evitare questa necessità è l'intenzione dei modelli ... – Aconcagua

+0

L'approccio di Essel è completamente diverso: le funzioni virtuali ordinarie accettano diverse istanze di template - e la funzione di template finale nella classe derivata serve solo a evitare la duplicazione del codice e non ha nemmeno una controparte in la classe base ... – Aconcagua