2009-07-06 19 views
22

Si consideri il seguente codice C++:Puntatori alle funzioni dei membri virtuali. Come funziona?

class A 
{ 
public: 
     virtual void f()=0; 
}; 


int main() 
{ 
    void (A::*f)()=&A::f; 
} 

Se avessi a indovinare, direi che & A :: F in questo contesto significherebbe "l'indirizzo della realizzazione di una di f()", poiché non esiste una separazione esplicita tra i puntatori alle funzioni membro regolari e le funzioni membro virtuali. E dal momento che A non implementa f(), sarebbe un errore di compilazione. Tuttavia, non lo è.

E non solo. Il seguente codice:

void (A::*f)()=&A::f; 
A *a=new B;   // B is a subclass of A, which implements f() 
(a->*f)(); 

chiamerà effettivamente B :: f.

Come mai?

+0

Perché il compilatore consente di farlo! Se chiamare un metodo normale non è diverso dal chiamare un metodo virtuale, perché pensi che il codice sia diverso quando si usano i puntatori del metodo. Secondo te, il compilatore sta traducendo le normali chiamate di metodo (virtuali e ordinate)? –

risposta

9

Ecco troppe informazioni sui puntatori funzione membro. C'è un po 'di cose sulle funzioni virtuali sotto "I compilatori ben fatti", anche se IIRC quando ho letto l'articolo che stavo sfiorando quella parte, dato che l'articolo riguarda l'implementazione dei delegati in C++.

http://www.codeproject.com/KB/cpp/FastDelegate.aspx

La risposta è che dipende dal compilatore, ma una possibilità è che il puntatore a funzione membro è implementato come una struttura che contiene un puntatore a una funzione "thunk" che rende la chiamata virtuale.

+0

ciao, hai appena indicato thunk. C'è del buon articolo che spiega il thunk? – anand

+0

"la parola thunk si riferisce a un pezzo di codice di basso livello, generalmente generato dalla macchina, che implementa alcuni dettagli di un particolare sistema software.", Da http://en.wikipedia.org/wiki/Thunk. Quella pagina tratta diversi tipi di thunk, anche se non questo particolare. –

22

Funziona perché lo standard dice che è come dovrebbe accadere. Ho fatto alcuni test con GCC, e si è scoperto per le funzioni virtuali, GCC memorizza l'offset della tabella virtuale della funzione in questione, in byte.

struct A { virtual void f() { } virtual void g() { } }; 
int main() { 
    union insp { 
    void (A::*pf)(); 
    ptrdiff_t pd[2]; 
    }; 
    insp p[] = { { &A::f }, { &A::g } }; 
    std::cout << p[0].pd[0] << " " 
      << p[1].pd[0] << std::endl; 
} 

Tale uscite del programma 1 5 - gli offset byte degli voci della tabella virtuali di queste due funzioni. Segue lo Itanium C++ ABI, which specifies that.

+0

Suppongo che la risposta alla mia domanda non sia C++ standardizzata. Tuttavia, lo sono anche i vtables, ma non conosco alcun compilatore che non usi vtables come meccanismo per le funzioni virtuali, quindi suppongo che ci sia anche un meccanismo * standard * per questo. La tua risposta mi rende solo più confuso.Se il compilatore memorizza 1 e 5 in puntatori alle funzioni membro di A, come può sapere se si tratta di un indice vtable o di un indirizzo reale? (si noti che non vi è alcuna differenza tra i puntatori alle funzioni regolari e membri virtuali) –

+0

Perché questa risposta ti rende più confuso? Basta chiedere se c'è qualcosa di poco chiaro. Questo genere di cose non è standardizzato. Spetta all'implementazione pensare ai modi per risolverlo. Possono decidere se è un puntatore a funzione o no: penso sia per questo che aggiungono 1. Quindi se il numero non è allineato, è un offset vtable. Se è allineato, è un puntatore a una funzione membro. Questa è solo una mia supposizione, però. –

+0

Grazie, sembra logico. Tuttavia, un po 'non efficiente per un'implementazione C++ ... Controllato il codice su VC, e i risultati sono completamente diversi. L'output è 'c01380 c01390', che sembra un indirizzo di qualcosa. –

1

Non sono del tutto sicuro, ma penso che sia solo un comportamento polimorfico regolare. Penso che &A::f in realtà significhi l'indirizzo del puntatore alla funzione nel vtable della classe, ed è per questo che non si ottiene un errore del compilatore. Lo spazio nel vtable è ancora allocato, e questa è la posizione in cui si sta effettivamente tornando.

Questo ha senso perché le classi derivate essenzialmente sovrascrivono questi valori con i puntatori alle loro funzioni. Questo è il motivo per cui (a->*f)() funziona nel secondo esempio: f fa riferimento al vtable implementato nella classe derivata.

+1

Questo potrebbe essere stato il caso se ci fosse una separazione tra i puntatori alle funzioni membro regolari e i puntatori alla funzione membro virtuale. Tuttavia, come ho detto, non c'è, ed è di questo che si tratta. –

+0

Un compilatore è definitivamente autorizzato a mettere tutti i metodi, virtuali o meno nel vtable. Se lo fa, può quindi utilizzare l'indice vtable per i puntatori alle funzioni membro. Abbastanza semplice per il compilatore - assicurati che un overrider non virtuale ottenga la propria voce vtable invece di sovrascrivere la voce della classe base. – MSalters

Problemi correlati