Table des méthodes virtuelles

Il ne faut pas confondre les méthodes virtuelles avec l'héritage virtuel bien que les deux utilisent le même mot clé virtual en C++. On parle de méthode virtuelle si la définition de la méthode contient le modificateur virtual avant le type de retour de la méthode. Lors de la redéfinition d'une méthode virtuelle, le modificateur virtual est optionnel mais la méthode reste toujours virtuelle. En revanche, il est possible de masquer une méthode non virtuelle par une méthode virtuelle.

    // Classe de Base
    class B {
    public:
      void f() { ... }          // Méthode non virtuelle
      virtual void x() { ... }  // Méthode virtuelle
      ...
    };

    // Classe dérivée
    class D : public B {
    public:
      virtual void f() { ... }  // Masquage d'une méthode non virtuelle par une méthode virtuelle 
      virtual void x() { ... }  // Redéfinition de méthode virtuelle
      // Aurait pu être écrit sans le modificateur virtual
      void x() { ... }          // Redéfinition de méthode virtuelle
    };
  

On parle d'héritage virtuel lorsque la classe de base est précédée du modificateur virtual comme ci-dessous. Ce type d'héritage est uniquement utile lors d'héritage multiple où une classe de base apparaît de manière multiple.

    // Classe de Base
    class B {
      ...
    };

    // Classe dérivée avec héritage virtuel
    class D : public virtual B {
      ...
    };
  

Disposition en mémoire

Sans méthode virtuelle

Tout objet d'une classe sans méthode virtuelle est représenté en mémoire par les représentations de chacun de ses attributs placés dans l'ordre de leurs déclarations. Il peut éventuellement y avoir des octets non utilisés entre certains attributs pour des raisons d'alignment.

    class B {
    public:
      void f() { ... }          // Méthode non virtuelle
      void g() { ... }          // Méthode non virtuelle
    private:
      int u;
      int v;
    };
  
          B
         +-------------+
         |      u      |
         +-------------+
         |      v      |
         +-------------+
  

Avec méthodes virtuelles

Tout objet d'une classe avec au moins une méthode virtuelle contient en plus des attributs un pointeur vptr vers une table des méthodes virtuelles. Ce pointeur vptr est en général placé avant les attributs. L'objet est donc représenté en mémoire par ce pointeur vptr suivi par les représentations de chacun des attributs placés dans l'ordre de leurs déclarations. La table des méthodes virtuelles contient une entrée pour chaque méthode virtuelle de la classe. La valeur de cette entrée est l'adresse du code de la méthode correspondante. Tous les objets d'une même classe partagent la même table. Le seul impact sur l'utilisation de la mémoire est que chaque objet possède un (ou plusieurs dans le cas de l'héritage multiple) pointeur vptr. Les tables occupent un espace mémoire négligeable. Le pointeur vptr est initialisé lors de l'initialisation globale de l'objet par un constructeur. Ce processus d'initialisation utilise pour chaque classe une table VTT contenant des pointeurs vers différentes tables nécessaires pour les étapes intermédiaires. Ces tables VTT ne sont pas abordées dans ce document.

    class B {
    public:
      void f() { ... }          // Méthode non virtuelle
      void g() { ... }          // Méthode non virtuelle
      virtual void x() { ... }  // Méthode virtuelle
      virtual void y() { ... }  // Méthode virtuelle
    private:
      int u;
      int v;
    };
  

Outre les pointeurs sur les méthodes virtuelles, les tables contiennent quelques informations supplémentaires. Elle contiennent une information RTTI (pour Run-Time Type Information) permettant de déterminer le type dynamique d'un objet. C'est souvent l'adresse du début de la table des méthodes virtuelles. Elles contiennent aussi l'information donnant l'offset top_offset entre la position du pointeur vptr et le début de l'objet. Cette information est uniquement utile pour l'héritage multiple.

                                         Vtable de B
                                       +-------------+
                                  +--->|      0      |      // Top-offset
                                  |    +-------------+
          B                       +----|     RTTI    |      // Run-time type information
         +-------------+               +-------------+
         |    vptr     |-------------->|    B::x()   |
         +-------------+               +-------------+
         |      u      |               |    B::y()   |
         +-------------+               +-------------+
         |      v      |
         +-------------+
  

L'appel de gcc avec l'option -fdump-class-hierarchy donne la sortie suivante si on ne garde que les classes pertinentes.

    Vtable for B
    B::_ZTV1B: 4u entries
    0     (int (*)(...))0
    8     (int (*)(...))(& _ZTI1B)
    16    (int (*)(...))B::x
    24    (int (*)(...))B::y

    Class B
       size=16 align=8
       base size=16 base align=8
    B (0x0x7fbd4749bf60) 0
        vptr=((& B::_ZTV1B) + 16u)
  

Appel de méthode

L'appel d'une méthode f() se déroule de manière suivante. On suppose que l'adresse de l'objet sur lequel est appelée la méthode est contenu dans un pointeur p de type B*. Il s'agit donc d'évaluer l'expression p->f(). L'objet en question est de type dynamique B ou d'un type dérivé.

Le compilateur vérifie d'abord que la classe B possède une méthode f() qui a été héritée d'une classe de base ou définie dans la classe B. Si cette méthode n'est pas virtuelle, c'est cette méthode qui est appelée quelque soit le type de l'objet. La méthode est donc appelé directement à son adresse. Si cette méthode est virtuelle la stratégie suivante est utilisée pour l'appeler. Cette stratégie permet que la méthode appelée dépende du type dynamique de l'objet.

Si la méthode f() est virtuelle, celle-ci correspond à une entrée dans la classe des méthodes virtuelles de la classe B. Le pointeur vptr de l'objet permet d'accéder à cette table des méthodes virtuelles et de récupérer l'adresse de la méthode f(). Cette dernière est alors utilisée pour appeler la méthode f().

    B* p = new B();
    p->x(...);                  // Compilé en (p->vptr[0])(...)  
    p->y(...);                  // Compilé en (p->vptr[1])(...)  
  

Héritage simple non virtuel

Soit D une classe qui dérive simplement d'une classe B. La représentation en mémoire d'un objet de classe D contient d'abord une repésentation d'un objet de classe B puis les attributs propres à la classe D. Le pointeur vptr de la classe B sert aussi pour la classe D. Il pointe alors vers la table des méthodes de la classe D. Cette table est organisée de la façon suivante. Elle contient d'abord une entrée pour chaque méthode virtuelle de la classe B (dans le même ordre que pour la classe B) puis une entrée pour chaque méthode propre à la classe D. L'entrée pour une méthode virtuelle héritée de la classe B est soit l'adresse du code de la méthode de la classe B si cette méthode n'est pas redéfinie dans la classe D soit l'adresse de la redéfinition dans la classe D s'il y a une redéfinition. Les entrées pour chaque méthode propre à la classe D sont les adresses du code des méthodes de la classe D.

    class B {
    public:
      void f() { ... }          // Méthode non virtuelle
      void g() { ... }          // Méthode non virtuelle
      virtual void x() { ... }  // Méthode virtuelle
      virtual void y() { ... }  // Méthode virtuelle
    private:
      int u;
      int v;
    };

    class D : public B {
    public:
      virtual void g() { ... }  // Masquage d'une méthode non virtuelle
      virtual void y() { ... }  // Redéfinition
      virtual void z() { ... }  // Méthode virtuelle 
    private:
      int w;
    };
  
                                        Vtable de D
                                       +-------------+
                                  +--->|      0      |      // Top-offset
                                  |    +-------------+
          D                       +----|     RTTI    |      // Run-time type information
         +-------------+               +-------------+                                 
         |    vptr     |-------------->|    B::x()   |      \                          
         +-------------+               +-------------+      |  Méthodes héritées de B  
         |      u      |               |    D::y()   |      /                          
         +-------------+               +-------------+                                 
         |      v      |               |    D::g()   |      \                          
         +=============+               +-------------+      |  Méthodes propres à D    
         |      w      |               |    D::z()   |      /                          
         +-------------+               +-------------+                                 
  

L'appel de gcc avec l'option -fdump-class-hierarchy donne la sortie suivante si on ne garde que les classes pertinentes.

    Vtable for D
    D::_ZTV1D: 6u entries
    0     (int (*)(...))0
    8     (int (*)(...))(& _ZTI1D)
    16    (int (*)(...))B::x
    24    (int (*)(...))D::y
    32    (int (*)(...))D::g
    40    (int (*)(...))D::z

    Class D
       size=24 align=8
       base size=20 base align=8
    D (0x0x7fbd47304478) 0
        vptr=((& D::_ZTV1D) + 16u)
      B (0x0x7fbd47319000) 0
          primary-for D (0x0x7fbd47304478)
  

Héritage multiple non virtuel

Soit D une classe qui dérive de deux classes B0 et B1. La représentation en mémoire d'un objet de classe D contient d'abord une représentation d'un objet de classe B1 avec son pointeur vptr, puis une représentation d'un objet de la classe B2 avec son pointeur vptr et finalement les représentations des attributs de la classe D. Un objet de classe D contient donc deux pointeurs vptr. Celui de la classe B1 pointe sur une première partie de la table des méthodes virtuelles de la classe D et celui de la classe B2 pointe sur une seconde partie. Le premier pointeur est utilisé lorsque l'objet est pointé par un pointeur de type B1* ou D* ou lorsqu'il est dans un emplacement de type D. Le second pointeur est utilisé lorsque l'objet est pointé par un pointeur de type B2*.

La première partie de la table contient d'abord une entrée pour chaque méthode virtuelle de la classe B1 (dans le même ordre que pour la classe B1), ensuite une entrée pour chaque méthode virtuelle de la classe B2 redéfinie dans la class D puis une entrée pour chaque méthode propre à la classe D. L'entrée pour une méthode virtuelle héritée de la classe B1 est soit l'adresse du code de la méthode de la classe B1 si cette méthode n'est pas redéfinie dans la classe D soit l'adresse de la redéfinition dans la classe D s'il y a une redéfinition. Les entrées sont ensuite les adresses des codes des méthodes de la classe D

La seconde partie de la table contient uniquement une entrée pour chaque méthode virtuelle de la classe B2 (dans le même ordre que pour la classe B2). L'entrée pour une méthode virtuelle héritée de la classe B1 est soit l'adresse du code de la méthode de la classe B si cette méthode n'est pas redéfinie dans la classe D soit l'adresse d'un fragment de code spécial si cette méthode est redéfinie dans la classe D. Le fragment de code spécial se charge de décaler le le pointeur this pour qu'il pointe sur le début de la représentation de l'objet de classe D avant d'appeler la redéfinition dans la classe D. Ce décalage est nécessaire car le code des méthodes de la classe D attendent que le pointeur this qu'elle reçoive pointe sur le début de la représentation d'un objet de classe D.

    class B1 {
    public:
      virtual void x() { ... }  // Méthode virtuelle
      virtual void y1() { ... } // Méthode virtuelle
      virtual void z1() { ... } // Méthode virtuelle
    private:
      int u;
      int v1;
    };

    class B2 {
    public:
      virtual void x() { ... }  // Méthode virtuelle
      virtual void y2() { ... } // Méthode virtuelle
      virtual void z2() { ... } // Méthode virtuelle
    private:
      int u;
      int v2;
    };

    class D : public B1, public B2 {
    public:
      virtual void x() { ... }  // Redéfinition
      virtual void z1() { ... } // Redéfinition
      virtual void z2() { ... } // Redéfinition
      virtual void t() { ... }  // Méthode propre à D
    private:
      int w;
    };

    class C : public D {
    public:
      virtual void y2() { ... } // Redéfinition
      virtual void z2() { ... } // Redéfinition
    private:
      int wc;
    };
  
                                         Vtable de B1
                                       +-------------+
                                  +--->|      0      |      // Top-offset
                                  |    +-------------+
          B1                      +----|     RTTI    |      // Run-time type information
         +-------------+               +-------------+
         |    vptr     |-------------->|   B1::x()   |
         +-------------+               +-------------+
         |      u      |               |   B1::y1()  |
         +-------------+               +-------------+
         |      v1     |               |   B1::z1()  |
         +-------------+               +-------------+


                                         Vtable de B2
                                       +-------------+
                                  +--->|      0      |      // Top-offset
                                  |    +-------------+
          B2                      +----|     RTTI    |      // Run-time type information
         +-------------+               +-------------+
         |    vptr     |-------------->|   B2::x()   |
         +-------------+               +-------------+
         |      u      |               |   B2::y2()  |
         +-------------+               +-------------+
         |      v2     |               |   B2::z2()  |
         +-------------+               +-------------+


                                         Vtable de D
                                       +-------------+
                                  +--->|      0      |      // Top-offset               
                                  |    +-------------+                                  
          D                       +----|     RTTI    |      // Run-time type information
         +-------------+          |    +-------------+                                 
         |    vptr     |----------+--->|    D::x()   |
         +-------------+          |    +-------------+
         |    B1::u    |          |    |   B1::y1()  |
         +-------------+          |    +-------------+
         |      v1     |          |    |    D::z1()  |
         +=============+          |    +-------------+
         |    vptr     |-----+    |    |    D::z2()  | 
         +-------------+     |    |    +-------------+ 
         |    B2::u    |     |    |    |    D::t()   |
         +-------------+     |    |    +=============+                        
         |      v2     |     |    |    |     -16     |      // Top-offset
         +=============+     |    |    +-------------+                                  
         |      w      |     |    +----|     RTTI    |      // Run-time type information
         +-------------+     |         +-------------+
                             +-------->|             |-------> this = this-16  
                                       +-------------+         D::x()         
                                       |   B2::y2()  |                        
                                       +-------------+                        
                                       |             |-------> this = this-16  
                                       +-------------+         D::z2()        


                                         Vtable de C
                                       +-------------+
                                  +--->|      0      |      // Top-offset               
                                  |    +-------------+                                  
          C                       +----|     RTTI    |      // Run-time type information
         +-------------+          |    +-------------+                                 
         |    vptr     |----------+--->|    D::x()   |
         +-------------+          |    +-------------+
         |    B1::u    |          |    |   B1::y1()  |
         +-------------+          |    +-------------+
         |      v1     |          |    |    D::z1()  |
         +=============+          |    +-------------+
         |    vptr     |-----+    |    |    C::z2()  | 
         +-------------+     |    |    +-------------+ 
         |    B2::u    |     |    |    |    D::t()   |
         +-------------+     |    |    +-------------+
         |      v2     |     |    |    |    C::y2()  |
         +=============+     |    |    +=============+                                    
         |      w      |     |    |    |     -16     |      // Top-offset                 
         +=============+     |    |    +-------------+
         |      wc     |     |    +----|     RTTI    |      // Run-time type information  
         +-------------+     |         +-------------+
                             +-------->|             |-----------> this = this-16                 
                                       +-------------+             D::x()
                                       |             |-------+                                    
                                       +-------------+       +---> this = this-16         
                                       |             |---+         C::y2()
                                       +-------------+   |               
                                                         +-------> this = this-16
                                                                   C::z2()
  
                                                           
    Vtable for B1
    B1::_ZTV2B1: 5u entries
    0     (int (*)(...))0
    8     (int (*)(...))(& _ZTI2B1)
    16    (int (*)(...))B1::x
    24    (int (*)(...))B1::y1
    32    (int (*)(...))B1::z1

    Class B1
       size=16 align=8
       base size=16 base align=8
    B1 (0x0x7f19ebdf8f60) 0
        vptr=((& B1::_ZTV2B1) + 16u)

    Vtable for B2
    B2::_ZTV2B2: 5u entries
    0     (int (*)(...))0
    8     (int (*)(...))(& _ZTI2B2)
    16    (int (*)(...))B2::x
    24    (int (*)(...))B2::y2
    32    (int (*)(...))B2::z2

    Class B2
       size=16 align=8
       base size=16 base align=8
    B2 (0x0x7f19ebc73000) 0
        vptr=((& B2::_ZTV2B2) + 16u)

    Vtable for D
    D::_ZTV1D: 12u entries
    0     (int (*)(...))0
    8     (int (*)(...))(& _ZTI1D)
    16    (int (*)(...))D::x
    24    (int (*)(...))B1::y1
    32    (int (*)(...))D::z1
    40    (int (*)(...))D::z2
    48    (int (*)(...))D::t
    56    (int (*)(...))-16
    64    (int (*)(...))(& _ZTI1D)
    72    (int (*)(...))D::_ZThn16_N1D1xEv
    80    (int (*)(...))B2::y2
    88    (int (*)(...))D::_ZThn16_N1D2z2Ev

    Class D
       size=40 align=8
       base size=36 base align=8
    D (0x0x7f19ebc57b60) 0
        vptr=((& D::_ZTV1D) + 16u)
      B1 (0x0x7f19ebc73060) 0
          primary-for D (0x0x7f19ebc57b60)
      B2 (0x0x7f19ebc730c0) 16
       vptr=((& D::_ZTV1D) + 72u)


    Vtable for C
    C::_ZTV1C: 13u entries
    0     (int (*)(...))0
    8     (int (*)(...))(& _ZTI1C)
    16    (int (*)(...))D::x
    24    (int (*)(...))B1::y1
    32    (int (*)(...))D::z1
    40    (int (*)(...))C::z2
    48    (int (*)(...))D::t
    56    (int (*)(...))C::y2
    64    (int (*)(...))-16
    72    (int (*)(...))(& _ZTI1C)
    80    (int (*)(...))D::_ZThn16_N1D1xEv
    88    (int (*)(...))C::_ZThn16_N1C2y2Ev
    96    (int (*)(...))C::_ZThn16_N1C2z2Ev

    Class C
       size=40 align=8
       base size=40 base align=8
    C (0x0x7f4c85a67478) 0
        vptr=((& C::_ZTV1C) + 16u)
      D (0x0x7f4c85a8f2a0) 0
          primary-for C (0x0x7f4c85a67478)
        B1 (0x0x7f4c85a7a120) 0
            primary-for D (0x0x7f4c85a8f2a0)
        B2 (0x0x7f4c85a7a180) 16
            vptr=((& C::_ZTV1C) + 80u)

  

La conversion d'une adresse d'un type dérivé vers une adresse d'un type de base impose d'ajuster l'adresse lorsque le type de base n'est pas celui de la première classe de base. Dans l'exemple ci-dessus, la conversion d'une adresse de type D* vers le type B2* nécessite de décaler l'adresse de la taille de B1. Par contre, la conversion d'une adresse de type D* vers le type B1* ne nécessite aucune décalage. Après l'exécution du fragment de programme ci-dessous avec les classes B1, B2 et D définies précédemment,

    D* dp = new D();
    B1* b1p = dp;
    B2* b2p = dp;
  

les deux pointeurs dp et b1p contiennent tous les deux l'adresse du début de l'objet de classe D créé. Au contraire, le pointeur b2p contient l'adresse du début de l'objet de classe B2 contenu dans l'objet de classe B qui est différent. L'affectation (c'est en fait une initialisation mais c'est identique dans ce cas) b2p = dp ne se contente pas de recopier dans b2p l'adresse contenue dans dp. Elle opère également un ajustement en lui ajoutant la différence top_offset entre les débuts des objets.

   D*    dp                   D             
    +---+                    +-------------+
    |   |-----------+------->|    vptr     |
    +---+          /         +-------------+
                  /          |    B1::u    |
   B1*  b1p      /           +-------------+
    +---+       /            |      v1     |
    |   |------+             +=============+
    +---+            +------>|    vptr     |
                    /        +-------------+
   B2*  b2p        /         |    B2::u    |
    +---+         /          +-------------+
    |   |--------+           |      v2     |
    +---+                    +=============+
                             |      w      |
                             +-------------+
  

Héritage simple virtuel

Dans l'héritage non virtuel, la représentation de la classe dérivée contient toujours la représentation de la classe de base. Au contraire, dans l'héritage virtuel, les deux représentations de la classe dérivée et de la classe de base sont dissociées. Elles ne partagent pas le pointeur vptr et elles possèdent chacune un pointeur vptr. Cette dissociation est utile lorsque la classe dérivée est elle-même utilisée comme classe de base d'une autre classe. Dans ce cas, la distance entre les deux représentations peut devenir différente. Cette distance appelée vbase_offset est aussi stockée dans la table des méthodes virtuelles. Elle est indispensable pour les conversions de pointeurs.

Soit D une classe qui dérive virtuellement d'une classe B. La représentation en mémoire d'un objet de classe D contient d'abord le pointeur vptr de la classe D suivi des attributs de la classe D puis une représentation de la classe B avec son pointeur vptr suivi des attributs de la classe B.

    class B {
    public:
      void f() { ... }          // Méthode non virtuelle
      void g() { ... }          // Méthode non virtuelle
      virtual void x() { ... }  // Méthode virtuelle
      virtual void y() { ... }  // Méthode virtuelle
    private:
      int u;
      int v;
    };

    class D : public virtual B {
    public:
      virtual void g() { ... }  // Masquage d'une méthode non virtuelle
      virtual void y() { ... }  // Redéfinition
      virtual void z() { ... }  // Méthode virtuelle 
    private:
      int w;
    };
  
                                         Vtable de B
                                       +-------------+
                                  +--->|      0      |      // Top-offset
                                  |    +-------------+
          B                       +----|     RTTI    |      // Run-time type information
         +-------------+               +-------------+
         |    vptr     |-------------->|    B::x()   |
         +-------------+               +-------------+
         |      u      |               |    B::y()   |
         +-------------+               +-------------+
         |      v      |
         +-------------+

                                         Vtable de D
                                       +-------------+
                                  +--->|     16      |      // Vbase-offset
                                  |    +-------------+
                                  |    |      0      |      // Top-offset               
                                  |    +-------------+                                  
          D                       +----|     RTTI    |      // Run-time type information
         +-------------+          |    +-------------+                                 
         |    vptr     |----------+--->|    D::g()   |
         +-------------+          |    +-------------+
         |     w       |          |    |    D::y()   |
         +=============+          |    +-------------+
         |    vptr     |-----+    |    |    D::z()   |
         +-------------+     |    |    +=============+
         |     u       |     |    |    |             | 
         +-------------+     |    |    +-------------+ 
         |     v       |     |    |    |      0      |      // Vbase-offset      
         +-------------+     |    |    +-------------+                        
                             |    |    |     -16     |      // Top-offset
                             |    |    +-------------+                                  
                             |    +----|     RTTI    |      // Run-time type information
                             |         +-------------+
                             +-------->|    B::x()   | 
                                       +-------------+
                                       |             |-------> this = this-16                         
                                       +-------------+         D::y()                                 
  
    Vtable for B
    B::_ZTV1B: 4u entries
    0     (int (*)(...))0
    8     (int (*)(...))(& _ZTI1B)
    16    (int (*)(...))B::x
    24    (int (*)(...))B::y

    Class B
       size=16 align=8
       base size=16 base align=8
    B (0x0x7f354d623f60) 0
        vptr=((& B::_ZTV1B) + 16u)

    Vtable for D
    D::_ZTV1D: 12u entries
    0     16u
    8     (int (*)(...))0
    16    (int (*)(...))(& _ZTI1D)
    24    (int (*)(...))D::g
    32    (int (*)(...))D::y
    40    (int (*)(...))D::z
    48    18446744073709551600u
    56    0u
    64    (int (*)(...))-16
    72    (int (*)(...))(& _ZTI1D)
    80    (int (*)(...))B::x
    88    (int (*)(...))D::_ZTv0_n32_N1D1yEv

    VTT for D
    D::_ZTT1D: 2u entries
    0     ((& D::_ZTV1D) + 24u)
    8     ((& D::_ZTV1D) + 80u)

    Class D
       size=32 align=8
       base size=12 base align=8
    D (0x0x7f354d48c478) 0
        vptridx=0u vptr=((& D::_ZTV1D) + 24u)
      B (0x0x7f354d4a1000) 16 virtual
          vptridx=8u vbaseoffset=-24 vptr=((& D::_ZTV1D) + 80u)
  

Héritage multiple virtuel

Dans l'exemple ci-dessous, les classes D0 et D1 héritent virtuellement de la même classe B. La classe C hérite ensuite des deux classes D0 et D1. La classe C contient donc une seule instance de la classe B. Celle-ci est placée après les instances des classes D0 et D1. La distance entre la représentation de la classe D0 incluse dans la classe C et celle de sa classe de base B incluse dans la classe C n'est pas la même que dans la disposition de la classe D0 seule. Dans le premier cas, la valeur de vbase_offset est 32 alors qu'il est seulement de 16 dans le second cas.

Le remplissage des tables est similaire à celui du cas non virtuel. La table contient soit l'adresse de la méthode héritée soit l'adresse de la redéfinition. Lorsque le début de la représentation de la classe de base ne coïncide pas avec le début de l'objet global, il faut ajouter un fragment de code qui ajuste le pointeur this.

    class B {
    public:
      virtual void x() { ... }  // Méthode virtuelle
      virtual void y() { ... }  // Méthode virtuelle
      virtual void z() { ... }  // Méthode virtuelle 
    private:
      int u;
    };

    class D0 : public virtual B {
    public:
      virtual void y() { ... }  // Redéfinition
      virtual void t0() { ... } // Méthode virtuelle 
    private:
      int v0;
    };

    class D1 : public virtual B {
    public:
      virtual void t1() { ... }  // Méthode virtuelle 
    private:
      int v1;
    };

    class C : public D0, public D1 {
    public:
      virtual void y() { ... }  // Redéfinition
      virtual void s() { ... }  // Méthode virtuelle 
    private:
      int w;
    };
  
                                         Vtable de B
                                       +-------------+
                                  +--->|      0      |      // Top-offset
                                  |    +-------------+
          B                       +----|     RTTI    |      // Run-time type information
         +-------------+               +-------------+
         |    vptr     |-------------->|    B::x()   |
         +-------------+               +-------------+
         |      u      |               |    B::y()   |
         +-------------+               +-------------+
                                       |    B::z()   |
                                       +-------------+
    

                                         Vtable de D0
                                       +-------------+
                                  +--->|     16      |      // Vbase-offset
                                  |    +-------------+
                                  |    |      0      |      // Top-offset               
                                  |    +-------------+                                  
          D0                      +----|     RTTI    |      // Run-time type information
         +-------------+          |    +-------------+                                 
         |    vptr     |----------+--->|   D0::y()   |
         +-------------+          |    +-------------+
         |     v0      |          |    |   D0::t0()  |
         +=============+          |    +=============+
         |    vptr     |-----+    |    |             |
         +-------------+     |    |    +-------------+
         |     u       |     |    |    |             | 
         +-------------+     |    |    +-------------+ 
                             |    |    |      0      |      // Vbase-offset      
                             |    |    +-------------+                        
                             |    |    |     -16     |      // Top-offset
                             |    |    +-------------+                                  
                             |    +----|     RTTI    |      // Run-time type information
                             |         +-------------+
                             +-------->|    B::x()   | 
                                       +-------------+
                                       |             |-------> this = this-16
                                       +-------------+         D0::y()
                                       |    B::z()   |
                                       +-------------+


                                         Vtable de D1
                                       +-------------+
                                  +--->|     16      |      // Vbase-offset
                                  |    +-------------+
                                  |    |      0      |      // Top-offset               
                                  |    +-------------+                                  
          D1                      +----|     RTTI    |      // Run-time type information
         +-------------+          |    +-------------+                                 
         |    vptr     |----------+--->|   D1::t1()  |
         +-------------+          |    +=============+
         |     v1      |          |    |             |                                   
         +=============+          |    +-------------+                                   
         |    vptr     |-----+    |    |             |                                   
         +-------------+     |    |    +-------------+                                   
         |     u       |     |    |    |      0      |      // Vbase-offset              
         +-------------+     |    |    +-------------+                                   
                             |    |    |     -16     |      // Top-offset                
                             |    |    +-------------+                                   
                             |    +----|     RTTI    |      // Run-time type information 
                             |         +-------------+                                   
                             +-------->|    B::x()   |
                                       +-------------+                                   
                                       |    B::y()   |
                                       +-------------+                                   
                                       |    B::z()   |
                                       +-------------+                                   
                        
                        
                                         Vtable de C
                                       +-------------+
                                   +-->|     32      |      // Vbase-offset
                                   |   +-------------+
                                   |   |      0      |      // Top-offset               
                                   |   +-------------+                                  
          C                        +---|     RTTI    |      // Run-time type information
         +-------------+           |   +-------------+                                 
         |    vptr     |-----------+-->|    C::y()   |
         +-------------+           |   +-------------+
         |     v0      |           |   |   D0::t0()  |                                   
         +=============+           |   +-------------+                                   
         |    vptr     |-------+   |   |    C::s()   |                                   
         +-------------+       |   |   +=============+                                   
         |     v1      |       |   |   |      16     |      // Vbase-offset
         +=============+       |   |   +-------------+                                   
         |      w      |       |   |   |     -16     |      // Top-offset
         +=============+       |   |   +-------------+                                   
         |    vptr     |---+   |   +---|     RTTI    |      // Run-time type information 
         +-------------+   |   |   |   +-------------+                                   
         |      u      |   |   +---+-->|   D1::t1()  |
         +-------------+   |       |   +=============+                                   
                           |       |   |             |
                           |       |   +-------------+                                   
                           |       |   |             |
                           |       |   +-------------+                                   
                           |       |   |      0      |      // Vbase-offset
                           |       |   +-------------+                                   
                           |       |   |     -32     |      // Top-offset
                           |       |   +-------------+                                   
                           |       +---|     RTTI    |      // Run-time type information
                           |           +-------------+                                   
                           +---------->|    B::x()   |
                                       +-------------+                                   
                                       |             |-------> this = this-32
                                       +-------------+         C::y()                 
                                       |    B::z()   |
                                       +-------------+                                   
  
    Vtable for B
    B::_ZTV1B: 5u entries
    0     (int (*)(...))0
    8     (int (*)(...))(& _ZTI1B)
    16    (int (*)(...))B::x
    24    (int (*)(...))B::y
    32    (int (*)(...))B::z

    Class B
       size=16 align=8
       base size=12 base align=8
    B (0x0x7f69339c1f60) 0
        vptr=((& B::_ZTV1B) + 16u)

    Vtable for D0
    D0::_ZTV2D0: 13u entries
    0     16u
    8     (int (*)(...))0
    16    (int (*)(...))(& _ZTI2D0)
    24    (int (*)(...))D0::y
    32    (int (*)(...))D0::t0
    40    0u
    48    18446744073709551600u
    56    0u
    64    (int (*)(...))-16
    72    (int (*)(...))(& _ZTI2D0)
    80    (int (*)(...))B::x
    88    (int (*)(...))D0::_ZTv0_n32_N2D01yEv
    96    (int (*)(...))B::z

    VTT for D0
    D0::_ZTT2D0: 2u entries
    0     ((& D0::_ZTV2D0) + 24u)
    8     ((& D0::_ZTV2D0) + 80u)

    Class D0
       size=32 align=8
       base size=12 base align=8
    D0 (0x0x7f693382b888) 0
        vptridx=0u vptr=((& D0::_ZTV2D0) + 24u)
      B (0x0x7f6933851060) 16 virtual
          vptridx=8u vbaseoffset=-24 vptr=((& D0::_ZTV2D0) + 80u)

    Vtable for D1
    D1::_ZTV2D1: 12u entries
    0     16u
    8     (int (*)(...))0
    16    (int (*)(...))(& _ZTI2D1)
    24    (int (*)(...))D1::t1
    32    0u
    40    0u
    48    0u
    56    (int (*)(...))-16
    64    (int (*)(...))(& _ZTI2D1)
    72    (int (*)(...))B::x
    80    (int (*)(...))B::y
    88    (int (*)(...))B::z

    VTT for D1
    D1::_ZTT2D1: 2u entries
    0     ((& D1::_ZTV2D1) + 24u)
    8     ((& D1::_ZTV2D1) + 72u)

    Class D1
       size=32 align=8
       base size=12 base align=8
    D1 (0x0x7f693382b9c0) 0
        vptridx=0u vptr=((& D1::_ZTV2D1) + 24u)
      B (0x0x7f69338510c0) 16 virtual
          vptridx=8u vbaseoffset=-24 vptr=((& D1::_ZTV2D1) + 72u)

    Vtable for C
    C::_ZTV1C: 18u entries
    0     32u
    8     (int (*)(...))0
    16    (int (*)(...))(& _ZTI1C)
    24    (int (*)(...))C::y
    32    (int (*)(...))D0::t0
    40    (int (*)(...))C::s
    48    16u
    56    (int (*)(...))-16
    64    (int (*)(...))(& _ZTI1C)
    72    (int (*)(...))D1::t1
    80    0u
    88    18446744073709551584u
    96    0u
    104   (int (*)(...))-32
    112   (int (*)(...))(& _ZTI1C)
    120   (int (*)(...))B::x
    128   (int (*)(...))C::_ZTv0_n32_N1C1yEv
    136   (int (*)(...))B::z

    Class C
       size=48 align=8
       base size=32 base align=8
    C (0x0x7f693384aaf0) 0
        vptridx=0u vptr=((& C::_ZTV1C) + 24u)
      D0 (0x0x7f693382ba90) 0
          primary-for C (0x0x7f693384aaf0)
          subvttidx=8u
        B (0x0x7f6933851120) 32 virtual
            vptridx=40u vbaseoffset=-24 vptr=((& C::_ZTV1C) + 120u)
      D1 (0x0x7f693382baf8) 16
          subvttidx=24u vptridx=48u vptr=((& C::_ZTV1C) + 72u)
        B (0x0x7f6933851120) alternative-path