2012-06-25 14 views
11

Le interfacce (classe polimorfica unicamente con funzioni virtuali pure) hanno un vtable? Dato che le interfacce non implementano una funzione polimorfica e non possono essere costruite direttamente, il linker non dovrebbe aver bisogno di posizionare un vtable. È così? Sono particolarmente preoccupato per il compilatore MSVC.Interfaccia vtable

+0

Una nota * molto * importante è la funzione 'declspec (novtable)' in MSVC: consente alle interfacce, in particolare a quelle COM, di omettere il vtable. Questo ha alcune implicazioni interessanti e significative quando si tratta di ereditarietà (forza l'ereditarietà singola, ma fa sì che l'oggetto finale abbia solo una singola tabella che fornisce più polimorfismo che in altro modo). – ssube

risposta

6

Sì, lo fanno. E ci sono una serie di buone ragioni per questo.

La prima buona ragione è che anche i metodi virtuali puri hanno un'implementazione. O implicito o esplicito. È relativamente facile trarre un trucco chiamando una pura funzione virtuale, quindi puoi fondamentalmente fornire una definizione per una tua, chiamarla e vedere cosa succede. Per questo motivo, dovrebbe esserci un tavolo virtuale in un primo momento.

C'è un altro motivo per inserire una tabella virtuale in una classe base anche se tutti i suoi metodi sono puramente virtuali e non ci sono altri membri di dati. Quando si utilizza il polimorfismo, un puntatore a una classe base viene passato tutto intorno al programma. Per chiamare un metodo virtuale, il compilatore/runtime dovrebbe calcolare l'offset relativo della tabella virtuale dal puntatore di base. Se C++ non avesse ereditarietà multipla, si potrebbe assumere un offset zero dalla classe base astratta (per esempio), nel qual caso sarebbe stato possibile non avere un vtable in quel punto (ma ne abbiamo ancora bisogno a causa del motivo n. 1). Ma poiché è coinvolta un'eredità multipla, un trucco "vtable è lì a 0 offset" non funzionerà perché potrebbero esserci due o tre vtables a seconda di un numero (e tipo) di classi base.

Ci potrebbero essere anche altri motivi per cui non ho comunque.

Spero che aiuti.

+0

Buona argomentazione! Non ci ho pensato. –

+2

Il trucco a cui ti riferisci, è quello che non richiede la pura funzione virtuale da definire (sotto _odr_ rules) o altrimenti causa un comportamento indefinito perché non ero a conoscenza di alcun trucco del genere? –

+3

Non sono convinto dal tuo primo motivo. Sono abbastanza sicuro che gli unici modi per chiamare una funzione virtuale pura sono (a) chiamarla non virtualmente, che non usa il vtable, o (b) invoca un comportamento indefinito chiamandolo dal costruttore/distruttore di una parte oggetto costruito, che non è necessario chiamarlo affatto. O conosci un altro trucco che io non conosco? –

5

Da un punto di vista puramente C++ è una domanda accademica. Le funzioni virtuali non devono essere implementate con vtables, se sono lì non c'è modo portatile per ottenerle.

Se si è particolarmente preoccupati per il compilatore MSVC, è possibile decorare le interfacce con __declspec(novtable).

(In generale, nelle implementazioni comuni, una classe astratta può avere bisogno di un vtable, ad es .:

struct Base { 
    Base(); 
    virtual void f() {} 
    virtual void g() = 0; 
}; 

void h(Base& b) { 
    b.f(); // Call f on a Base that is not (yet) a Derived 
      // vtable for Base required 
} 

Base::Base() { 
    h(*this); 
} 

struct Derived : Base { 
    void g() {} 
}; 

int main() { 
    Derived d; 
} 

)

+0

Voglio vedere come possono essere implementati non usando tabelle virtuali o qualsiasi altro approccio in cui almeno sizeof (void *) deve essere aggiunto a una classe che serve come "punto di partenza" ... –

+1

@VladLazarenko: Penso che sia quasi universalmente accettato che una soluzione basata su vptr/vtable sia ottimale ma implementazioni impraticabili come la memorizzazione di una mappa di indirizzo in informazioni di tipo dinamico di qualche forma sono almeno teoricamente possibili. –

+0

Hai capito il punto.Penso che ciò che intendo sia che, indipendentemente da come lo chiami, avrai bisogno almeno di un qualche punto di partenza per accedervi, sia che si tratti di un puntatore a vtable, un puntatore all'hash con indirizzi o hash stesso a qualche hash globale con le mappe per funzionare puntatori. –

1

Il vtable non è necessario, ma raramente ottimizzato. MSVC fornisce l'estensione __declspec(novtable), che dice esplicitamente al compilatore che il vtable può essere rimosso. In assenza di ciò, il compilatore dovrebbe verificare che il vtable non sia usato. Questo non è eccezionalmente difficile, ma ancora lontano dal banale. E poiché non fornisce vantaggi di velocità reale nel codice normale, il controllo non è implementato in nessun compilatore che conosca.