Pour observer concrètement la disposition mémoire, nous utilisons l'option de compilation GCC -fdump-lang-class.
Considérons le code suivant :
class ClasseBase {
public:
ClasseBase() {}
virtual ~ClasseBase() {}
private:
int mem;
};
En compilant avec la commande :
g++ -O0 -std=c++11 -fdump-lang-class test.cpp
Un fichier décrivant la disposition mémoire est généré : a-test.cpp.001l.class.
Vtable for ClasseBase
ClasseBase::_ZTV10ClasseBase: 4 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI10ClasseBase)
16 (int (*)(...))ClasseBase::~ClasseBase
24 (int (*)(...))ClasseBase::~ClasseBase
Class ClasseBase
size=16 align=8
base size=12 base align=8
ClasseBase (0x0x7eafaf550420) 0
vptr=((& ClasseBase::_ZTV10ClasseBase) + 16)
D'après le modèle objet C++ décrit par Lippman, une classe contenant des fonctions virtuelles inclut un pointeur vers la table des fonctions virtuelles (vtable). La classe ClasseBase contient un membre entier mem et ce pointeur vptr. En raison de l'alignement, sa taille totale est de 16 octets.
La vtable comporte quatre pointeurs, chacun de taille 8 octets :
- Le premier est un offset indiquant le déplacement du pointeur de vtable.
- Le second pointe vers l'objet typeinfo, utilisé pour le RTTI.
- Le troisième et le quatrième sont respectivement les destructeurs "complete object" et "deleting".
Modèle objet avec héritage simple
Examinons un scénario d'héritage simple :
class ClasseBase {
public:
ClasseBase() {}
virtual ~ClasseBase() {}
private:
int mem;
};
class ClasseDerivee : public ClasseBase {
public:
ClasseDerivee(int n) {}
virtual ~ClasseDerivee() {}
virtual void obtenir() {}
virtual void definir() {}
private:
int val;
};
class ClasseUnique : public ClasseDerivee {
public:
ClasseUnique(int n) {}
void definir() override {}
virtual void afficher() {}
private:
int donn;
};
L'analyse de la compilation produit les structures suivantes :
Vtable for ClasseBase
ClasseBase::_ZTV10ClasseBase: 4 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI10ClasseBase)
16 (int (*)(...))ClasseBase::~ClasseBase
24 (int (*)(...))ClasseBase::~ClasseBase
Class ClasseBase
size=16 align=8
base size=12 base align=8
ClasseBase (0x0x7a6689b50420) 0
vptr=((& ClasseBase::_ZTV10ClasseBase) + 16)
Vtable for ClasseDerivee
ClasseDerivee::_ZTV14ClasseDerivee: 6 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI14ClasseDerivee)
16 (int (*)(...))ClasseDerivee::~ClasseDerivee
24 (int (*)(...))ClasseDerivee::~ClasseDerivee
32 (int (*)(...))ClasseDerivee::obtenir
40 (int (*)(...))ClasseDerivee::definir
Class ClasseDerivee
size=16 align=8
base size=16 base align=8
ClasseDerivee (0x0x7a6689a0e1a0) 0
vptr=((& ClasseDerivee::_ZTV14ClasseDerivee) + 16)
ClasseBase (0x0x7a6689b508a0) 0
primary-for ClasseDerivee (0x0x7a6689a0e1a0)
Vtable for ClasseUnique
ClasseUnique::_ZTV12ClasseUnique: 7 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI12ClasseUnique)
16 (int (*)(...))ClasseUnique::~ClasseUnique
24 (int (*)(...))ClasseUnique::~ClasseUnique
32 (int (*)(...))ClasseDerivee::obtenir
40 (int (*)(...))ClasseUnique::definir
48 (int (*)(...))ClasseUnique::afficher
Class ClasseUnique
size=24 align=8
base size=20 base align=8
ClasseUnique (0x0x7a6689a0e208) 0
vptr=((& ClasseUnique::_ZTV12ClasseUnique) + 16)
ClasseDerivee (0x0x7a6689a0e270) 0
primary-for ClasseUnique (0x0x7a6689a0e208)
ClasseBase (0x0x7a6689b50d80) 0
primary-for ClasseDerivee (0x0x7a6689a0e270)
Dans un scénario d'héritage simple, la classe dérivée possède une seule vtable. Celle-ci est une copie de la table de la classe de base, où les fonctions virtuelles redéfinies par override remplacent les entrées correspondantes. Les fonctions virtuelles propres à la classe dérivée sont ajoutées à la fin de la table.
Les nouveaux membres non statiques de la classe dérivée sont placés après les membres de la classe de base. Les membres statiques sont stockés dans la section .bss (segment de données non initialisées).
Modèle objet avec héritage multiple
Considérons un cas d'héritage multiple non diamant :
class BaseA {
public:
BaseA(int n) {}
virtual ~BaseA() {}
int obtenir() {}
virtual void definir() {}
static void compterA() {}
private:
int a;
static int aa;
};
class BaseB {
public:
BaseB(int n) {}
virtual ~BaseB() {}
int obtenir() {}
virtual void definir() {}
virtual void ajouter() {}
static void compterB() {}
private:
int b;
static int bb;
};
class DeriveMultiple : public BaseA, public BaseB {
public:
DeriveMultiple(int n) {}
void ajouter() override {}
void definir() override {}
virtual void presenter() {}
private:
int c;
};
La compilation génère les structures mémoire suivantes :
Vtable for BaseA
BaseA::_ZTV5BaseA: 5 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5BaseA)
16 (int (*)(...))BaseA::~BaseA
24 (int (*)(...))BaseA::~BaseA
32 (int (*)(...))BaseA::definir
Class BaseA
size=16 align=8
base size=12 base align=8
BaseA (0x0x7983aa950420) 0
vptr=((& BaseA::_ZTV5BaseA) + 16)
Vtable for BaseB
BaseB::_ZTV5BaseB: 6 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI5BaseB)
16 (int (*)(...))BaseB::~BaseB
24 (int (*)(...))BaseB::~BaseB
32 (int (*)(...))BaseB::definir
40 (int (*)(...))BaseB::ajouter
Class BaseB
size=16 align=8
base size=12 base align=8
BaseB (0x0x7983aa9509c0) 0
vptr=((& BaseB::_ZTV5BaseB) + 16)
Vtable for DeriveMultiple
DeriveMultiple::_ZTV15DeriveMultiple: 13 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI15DeriveMultiple)
16 (int (*)(...))DeriveMultiple::~DeriveMultiple
24 (int (*)(...))DeriveMultiple::~DeriveMultiple
32 (int (*)(...))DeriveMultiple::definir
40 (int (*)(...))DeriveMultiple::ajouter
48 (int (*)(...))DeriveMultiple::presenter
56 (int (*)(...))-16
64 (int (*)(...))(& _ZTI15DeriveMultiple)
72 (int (*)(...))DeriveMultiple::_ZThn16_N15DeriveMultipleD1Ev
80 (int (*)(...))DeriveMultiple::_ZThn16_N15DeriveMultipleD0Ev
88 (int (*)(...))DeriveMultiple::_ZThn16_N15DeriveMultiple5definirEv
96 (int (*)(...))DeriveMultiple::_ZThn16_N15DeriveMultiple7ajouterEv
Class DeriveMultiple
size=32 align=8
base size=32 base align=8
DeriveMultiple (0x0x7983aa963930) 0
vptr=((& DeriveMultiple::_ZTV15DeriveMultiple) + 16)
BaseA (0x0x7983aa950f00) 0
primary-for DeriveMultiple (0x0x7983aa963930)
BaseB (0x0x7983aa950f60) 16
vptr=((& DeriveMultiple::_ZTV15DeriveMultiple) + 72)
On observe que la classe dérivée contient deux pointeurs de vtable (vptr), un pour chaque classe de base.
Propriété enable_shared_from_this
Cette caractéristique permet à une instance de posséder une référence faible vers elle-même, facilitant la création de std::shared_ptr à partir de this.
class DeriveAvecSharedPtr : public ClasseBase,
public std::enable_shared_from_this<DeriveAvecSharedPtr> {
public:
DeriveAvecSharedPtr(int n) {}
void definir() override;
virtual void afficher();
private:
int donnee;
};
La taille mémoire de la classe augmente de 16 octets en raison de l'héritage de std::enable_shared_from_this. Cette classe stocke en interne deux pointeurs, comme en témoigne l'implémentation de std::weak_ptr.
L'ajout de enable_shared_from_this n'affecte pas le contenu de la vtable de la classe dérivée.