Question

Je suis un peu confus au sujet du vptr et de la représentation des objets dans la mémoire, et j'espère que vous pourrez m'aider à mieux comprendre le problème.

  1. Considérer B hérite de A et les deux définissent des fonctions virtuelles f().D'après ce que j'ai appris, la représentation d'un objet de classe B dans la mémoire ressemble à ceci :[ vptr | A | B ]et le vtbl que vptr pointe vers contient B::f().J'ai aussi compris que lancer l'objet depuis B à A ne fait rien sauf ignorer le B partie à la fin de l'objet.Est-ce vrai?Ce comportement n'est-il pas mauvais ?Nous voulons cet objet de type A éxécuter A::f() méthode et non B::f().

  2. Y a-t-il un certain nombre de vtables dans le système comme le nombre de classes ?

  3. Comment un vtable à quoi ressemble une classe qui hérite de deux classes ou plus ?Comment l’objet de C sera-t-il représenté dans la mémoire ?

  4. Identique à la question 3 mais avec héritage virtuel.

Était-ce utile?

La solution

Ce qui suit est vrai pour GCC (et cela semble vrai pour LLVM) lien), mais cela peut également être vrai pour le compilateur que vous utilisez.Tout cela dépend de l’implémentation et n’est pas régi par la norme C++.Cependant, GCC écrit son propre document standard binaire, Itanium ABI.

J'ai essayé d'expliquer les concepts de base de la façon dont les tables virtuelles sont disposées avec des mots plus simples dans le cadre de mon article sur les performances des fonctions virtuelles en C++, qui pourrait vous être utile.Voici les réponses à vos questions :

  1. Une manière plus correcte de représenter la représentation interne de l'objet est la suivante :

    | vptr | ======= | ======= |  <-- your object
           |----A----|         |
           |---------B---------|
    

    B contient sa classe de base A, il ajoute simplement quelques-uns de ses propres membres après sa fin.

    Casting à partir de B* à A* en effet ne fait rien, il renvoie le même pointeur, et vptr reste le même.Mais, en un mot, les fonctions virtuelles ne sont pas toujours appelées via vtable.Parfois, elles sont appelées comme les autres fonctions.

    Voici une explication plus détaillée.Vous devez distinguer deux manières d’appeler une fonction membre :

    A a, *aptr;
    a.func();         // the call to A::func() is precompiled!
    aptr->A::func();  // ditto
    aptr->func();     // calls virtual function through vtable.
                      // It may be a call to A::func() or B::func().
    

    Le truc c'est que c'est connu au moment de la compilation comment la fonction sera appelée :via vtable ou ce sera simplement un appel habituel.Et le truc c'est que le type d'une expression de casting est connu au moment de la compilation, et donc le compilateur choisit la bonne fonction au moment de la compilation.

    B b, *bptr;          
    static_cast<A>(b)::func(); //calls A::func, because the type
       // of static_cast<A>(b) is A!
    

    Dans ce cas, il ne regarde même pas dans vtable !

  2. En général, non.Une classe peut avoir plusieurs vtables si elle hérite de plusieurs bases, chacune ayant sa propre vtable.Un tel ensemble de tables virtuelles forme un « groupe de tables virtuelles » (voir pt.3).

    La classe a également besoin d'un ensemble de tables virtuelles de construction, pour répartir correctement les fonctions virtuelles lors de la construction des bases d'un objet complexe.Vous pouvez lire plus loin dans la norme que j'ai liée.

  3. Voici un exemple.Supposer C hérite de A et B, chaque classe définissant virtual void func(), ainsi que a,b ou c fonction virtuelle pertinente à son nom.

    Le C aura un groupe vtable de deux vtables.Il partagera une table virtuelle avec A (la table virtuelle où vont les propres fonctions de la classe actuelle est appelée "primaire"), et une table virtuelle pour B sera ajouté :

    | C::func()   |   a()  |  c()  || C::func()  |   b()   |
    |---- vtable for A ----|        |---- vtable for B ----| 
    |--- "primary virtual table" --||- "secondary vtable" -|
    |-------------- virtual table group for C -------------|
    

    La représentation de l'objet en mémoire ressemblera presque à celle de sa table virtuelle.Ajoutez simplement un vptr avant chaque table virtuelle d'un groupe, et vous aurez une estimation approximative de la façon dont les données sont disposées à l'intérieur de l'objet.Vous pouvez en lire davantage dans le section pertinente du standard binaire GCC.

  4. Les bases virtuelles (certaines d'entre elles) sont disposées à la fin du groupe vtable.Ceci est dû au fait que chaque classe ne devrait avoir qu'une seule base virtuelle, et si elles étaient mélangées avec des tables virtuelles "habituelles", alors le compilateur ne pourrait pas réutiliser des parties des tables virtuelles construites pour créer celles des classes dérivées.Cela conduirait à calculer des décalages inutiles et diminuerait les performances.

    Grâce à un tel placement, les bases virtuelles introduisent également des éléments supplémentaires dans leurs vtables : vcall offset (pour obtenir l'adresse d'un remplacement final lors du passage du pointeur vers une base virtuelle à l'intérieur d'un objet complet jusqu'au début de la classe qui remplace la fonction virtuelle) pour chaque fonction virtuelle qui y est définie.De plus, chaque base virtuelle ajoute vbase les décalages, qui sont insérés dans la vtable de la classe dérivée ;ils permettent de retrouver où commencent les données de la base virtuelle (elles ne peuvent pas être précompilées puisque l'adresse réelle dépend de la hiérarchie :les bases virtuelles se trouvent à la fin de l'objet, et le décalage depuis le début varie en fonction du nombre de classes non virtuelles dont hérite la classe actuelle.).

Woof, j'espère ne pas avoir introduit beaucoup de complexité inutile.Dans tous les cas, vous pouvez vous référer au standard original, ou à tout document de votre propre compilateur.

Autres conseils

  1. Cela me semble correct.Ce n'est pas faux, car si vous utilisez un pointeur A, vous n'avez besoin que de ce que A fournit et peut-être des implémentations de fonctions B disponibles à partir de la table virtuelle A (il peut y avoir plusieurs tables virtuelles, en fonction de la complexité du compilateur et de la hiérarchie).
  2. Je dirais oui, mais cela dépend de l'implémentation du compilateur, vous n'avez donc pas vraiment besoin de le savoir.
  3. et 4.Lisez plus loin.

Je recommanderais de lire L'héritage multiple considéré comme utile , c'est un article long mais il clarifie les choses sur le sujet car il explique en détail comment fonctionne l'héritage en C++ (les liens vers les figures ne fonctionnent pas mais ils sont disponibles en bas de page).

  1. Si l'objet B hérite de A alors la représentation mémoire de B sera la suivante :

    • pointeur vers la table virtuelle de A
    • Des variables/fonctions spécifiques
    • pointeur vers la table virtuelle de B
    • B variables/fonctions/remplacements spécifiques

    Si vous avez B* b = new B();(A)b->f() alors :

    • si f a été déclaré comme fonction virtuelle alors l'implémentation de B est appelée car b est de type B
    • si f n'a pas été déclaré comme fonction virtuelle, une fois appelé, il n'y aura pas de recherche dans la table virtuelle pour l'implémentation correcte et l'implémentation de A sera appelée.
  2. Chaque objet aura sa propre table virtuelle (ne prenez pas cela pour acquis, car je dois le rechercher

  3. Jeter un coup d'œil à ce pour un exemple de configuration de table virtuelle lorsqu'il s'agit d'héritage multiple

  4. Voir ce pour une discussion sur l'héritage du diamant et la représentation vtable

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top