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?
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?
Risposte:
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 .
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.
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.
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
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 .)
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.
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.
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 .
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:
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.
Foo
puntatore è qualificato come Foo<Bar>
, non può puntare a Foo<Barf>
o Foo<XXX>
.
No, le funzioni dei membri del modello non possono essere virtuali.
Nelle altre risposte la funzione modello proposta è una facciata e non offre alcun vantaggio pratico.
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
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.
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 ( int
e 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 TMethod
nelle 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::Method
realtà è un surplus per il codice reale ( VMethod
può essere reso pubblico e utilizzato direttamente). L'ho aggiunto in modo che sembri un vero e proprio metodo di modello "virtuale".
Base
classe 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 ...
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.
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
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;
}