2013-08-21 17 views
30

considerare questo semplice gerarchia:Come è dynamic_cast implementato

class Base { public: virtual ~Base() { } }; 
class Derived : public Base { }; 

Cercando di downcast Base* p-Derived* è possibile utilizzando dynamic_cast<Derived*>(p). Ero abituato a pensare che dynamic_cast funzioni confrontando il puntatore vtable in p con quello in un oggetto Derived.

Ma cosa succede se deriviamo un'altra classe da Derived? Ora abbiamo:

class Derived2 : public Derived { }; 

In questo caso:

Base* base = new Derived2; 
Derived* derived = dynamic_cast<Derived*>(base); 

Abbiamo ancora ottenere un successo abbattuta, anche se il puntatore vtable in Derived2 non ha nulla a che fare con un puntatore vtable in Derived.

Come funziona? Come può il dynamic_cast sapere se Derived2 è stato derivato da Derived (cosa succede se Derived è stato dichiarato in una libreria diversa)?

I am in cerca di dettagli specifici su come funziona (preferibilmente in GCC, ma anche altri vanno bene). Questa domanda è non un duplicato di this question (che non specifica come effettivamente funziona).

+2

Potrebbe essere implementata in modo diverso nei diversi compilatori, per essere sicuri che si potrebbe desiderare di leggere la fonte della loro ... – PlasmaHH

risposta

24

Come può la dynamic_cast sapere se Derived2 è stato derivato da Derived (cosa succede se Derived dichiarato in una libreria diversa)?

La risposta a questo è sorprendentemente semplice: dynamic_cast può sapere questo mantenendo questa conoscenza in giro.

Quando il compilatore genera il codice, mantiene i dati relativi alle gerarchie di classi in una sorta di tabella che può essere consultata in seguito da dynamic_cast. Quella tabella può essere collegata al puntatore vtable per una facile ricerca dall'implementazione dynamic_cast. I dati necessari per typeid per quelle classi possono anche essere memorizzati insieme a quelli.

Se le librerie sono coinvolte, questo genere di cose di solito richiede che queste strutture di informazioni di tipo siano esposte nelle librerie, proprio come con le funzioni. Ad esempio, è possibile ottenere un errore del linker simile a "Riferimento non definito a 'vtable for XXX'" (e boy, sono quelli fastidiosi!), Di nuovo, proprio come con le funzioni.

+0

Ci sono alcune complicazioni quando si tratta di ereditarietà multipla. Vedi ad esempio [questa domanda] (http://stackoverflow.com/questions/5712808). –

13

Magico.

Sto scherzando. Se vuoi veramente approfondire questa ricerca, il codice che lo implementa per GCC è in libsupC++, una parte di libstdC++.

https://github.com/mirrors/gcc/tree/master/libstdc%2B%2B-v3/libsupc%2B%2B

In particolare, cercare tutti i file con TINFO o type_info nel loro nome.

O leggere la descrizione qui, questo è probabilmente molto più accessibile:

https://itanium-cxx-abi.github.io/cxx-abi/abi.html#rtti

questi dati il ​​formato delle informazioni tipo il compilatore genera e dovrebbe darvi indizi come il supporto runtime trova poi la destra percorso di lancio.

+1

Grazie! Di particolare interesse è la sezione * 2.9.5 * che contiene la descrizione della struttura RTTI (nota anche come v-table) e la codifica della gerarchia (e naturalmente * 2.9.7 * che descrive l'algoritmo stesso). –

+0

@SteveTownsend Grazie, aggiornato. –

+0

impressionante, grazie Sebastian –

2

Come può il dynamic_cast sapere se Derived2 è stato derivato da Derived (cosa succede se Derived è stato dichiarato in una libreria diversa)?

Lo stesso dynamic_cast non sa nulla, è il compilatore che conosce quei fatti. Un vtable non contiene necessariamente solo i riferimenti alle funzioni virtuali.

Ecco come vorrei (ingenuamente) farlo: il mio vtable conterrà i puntatori a qualche tipo di informazione (RTTI) usata da dynamic_cast. Il RTTI per un tipo conterrà i puntatori alle classi base, quindi posso risalire alla gerarchia delle classi. Pseudocodice per il cast sarebbe simile a questa:

Base* base = new Derived2; //base->vptr[RTTI_index] points to RTTI_of(Derived2) 

//dynamic_cast<Derived*>(base): 
RTTI* pRTTI = base->vptr[RTTI_index]; 
while (pRTTI && *pRTTI != RTTI_of(Derived)) 
{ 
    pRTTI = pRTTI->ParentRTTI; 
} 
if (pRTTI) return (Derived*)(base); 
return NULL; 
+1

Abbastanza ingenuo, in effetti, poiché non riesce a tenere conto di più classi base, classi base virtuali (quelle non divertenti) e adattamento offset. L'idea di base è comunque buona: la topologia dell'oggetto è codificata nella sua tabella V. –

+0

@MatthieuM. questo è quello che volevo: descrivere l'idea di base. Avevo in mente le altre cose (eccetto le classi base virtuali, fanculo!) Ma non mi sono preoccupato di scrivere tonnellate di pseudocodice inutile ;-) –

+0

Per un'implementazione di riferimento, ho trovato uno sorprendentemente abbastanza leggibile in [libcxxrt] (https: //github.com/pathscale/libcxxrt/blob/master/src/dynamic_cast.cc), particolarmente chiaro in combinazione con il link di [Sebastian Redl] (http://mentorembedded.github.io/cxx-abi/abi. html # rtti) ovvero il layout della struttura RTTI nella sezione 2.9.5 di Itanium ABI. –