Nel tuo esempio, la profondità dell'albero di ereditarietà non influisce sulle prestazioni. La ragione è semplice, ogni istanza ha il suo puntatore a qualche vtable (contenente i puntatori di funzione), e una chiamata di funzione virtuale va solo attraverso quel vtable.
In altre parole, il tuo codice funziona come il seguente codice C:
struct vtable {
void (*fooptr) (struct D1*);
};
struct D1 {
const struct vtable*vptr;
};
struct D2 {
const struct vtable*vptr;
};
E ad es. baseptr->foo()
è "trasformato" al tempo di compilazione a baseptr->vptr.fooptr(baseptr)
(e ovviamente d1ptr->foo()
è "trasformato" in d1ptr->vptr.fooptr(d1ptr)
ecc ...)
Quindi il costo di esecuzione è lo stesso: prendi il vtable e chiama indirettamente il puntatore alla funzione all'interno. Il costo rimane lo stesso anche se disponi di una sottoclasse struct D4
di struct D3
sottoclasse di struct D2
sottoclasse di struct D1
. Potresti avere un struct D99
con 99 ereditari e il costo di esecuzione sarebbe rimasto lo stesso.
Su alcuni processori, qualsiasi chiamata indiretta potrebbe essere più lenta di una chiamata diretta , ad es. a causa del predittore di ramo costo
I dati del vtable sono costruiti al momento della compilazione (come dati statici costanti).
Le cose diventano un po 'più complesse con ereditarietà virtuale e / o ereditarietà multipla
BTW, alcuni framework di oggetti C (ad es. GObject di Gtk, o diverse strutture di dati nel kernel di Linux) fornire i loro dati vtable (o di classe). Puntare a una struttura dati contenente puntatori di funzione è abbastanza comune (anche in C). Leggi anche le chiusure e vedrai una relazione con "vtables" (sia le chiusure che gli oggetti si mescolano codice con dati).
Alcuni compilatori C ++ (ad esempio GCC ) possono fornire devirtualization come tecnica di ottimizzazione.