Je veux savoir ce qu'est une " classe de base virtuelle " et ce qu'elle signifie.
Permettez-moi de vous montrer un exemple:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
c++
virtual-inheritance
popopome
la source
la source
Réponses:
Les classes de base virtuelles, utilisées dans l'héritage virtuel, sont un moyen d'empêcher que plusieurs "instances" d'une classe donnée n'apparaissent dans une hiérarchie d'héritage lors de l'utilisation de l'héritage multiple.
Considérez le scénario suivant:
La hiérarchie des classes ci-dessus donne le "diamant redouté" qui ressemble à ceci:
Une instance de D sera composée de B, qui comprend A, et C qui comprend également A. Donc, vous avez deux "instances" (à défaut d'une meilleure expression) de A.
Lorsque vous avez ce scénario, vous avez la possibilité d'ambiguïté. Que se passe-t-il lorsque vous faites cela:
L'héritage virtuel est là pour résoudre ce problème. Lorsque vous spécifiez virtual lors de l'héritage de vos classes, vous dites au compilateur que vous ne voulez qu'une seule instance.
Cela signifie qu'il n'y a qu'une seule "instance" de A incluse dans la hiérarchie. Par conséquent
Ceci est un mini résumé. Pour plus d'informations, lisez ceci et ceci . Un bon exemple est également disponible ici .
la source
virtual
, la disposition des objets ressemble au diamant; et si nous ne l'utilisons pas,virtual
la disposition de l'objet ressemble à une structure arborescente qui contient deuxA
sÀ propos de la disposition de la mémoire
En remarque, le problème avec le diamant redouté est que la classe de base est présente plusieurs fois. Donc, avec un héritage régulier, vous pensez avoir:
Mais dans la disposition de la mémoire, vous avez:
Cela explique pourquoi lors d'un appel
D::foo()
, vous avez un problème d'ambiguïté. Mais le vrai problème survient lorsque vous souhaitez utiliser une variable membre deA
. Par exemple, disons que nous avons:Lorsque vous essayez d'accéder à
m_iValue
partir deD
, le compilateur protestera, car dans la hiérarchie, il en verra deuxm_iValue
, pas un. Et si vous en modifiez un, disonsB::m_iValue
(c'est leA::m_iValue
parent deB
),C::m_iValue
il ne sera pas modifié (c'est leA::m_iValue
parent deC
).C'est là que l'héritage virtuel est utile, car avec lui, vous reviendrez à une véritable disposition en losanges, avec non seulement une
foo()
méthode, mais aussi une et une seulem_iValue
.Qu'est-ce qui pourrait mal se passer?
Imaginer:
A
a une fonctionnalité de base.B
y ajoute une sorte de tableau de données sympa (par exemple)C
lui ajoute une fonctionnalité intéressante comme un motif d'observateur (par exemple, activém_iValue
).D
hérite deB
etC
, et donc deA
.Avec l'héritage normal, la modification à
m_iValue
partir deD
est ambiguë et cela doit être résolu. Même si c'est le cas, il y en a deux à l'm_iValues
intérieurD
, il vaut donc mieux s'en souvenir et mettre à jour les deux en même temps.Avec l'héritage virtuel, la modification
m_iValue
depuisD
est ok ... Mais ... Disons que vous l'avez faitD
. Grâce à sonC
interface, vous avez attaché un observateur. Et grâce à sonB
interface, vous mettez à jour le tableau cool, ce qui a pour effet secondaire de changer directementm_iValue
...Comme le changement de
m_iValue
se fait directement (sans utiliser de méthode d'accesseur virtuel), l'observateur "écoutant"C
ne sera pas appelé, car le code implémentant l'écoute est dedansC
, etB
ne le sait pas ...Conclusion
Si vous avez un diamant dans votre hiérarchie, cela signifie que vous avez 95% de chances d'avoir fait quelque chose de mal avec cette hiérarchie.
la source
Expliquer l'héritage multiple avec des bases virtuelles nécessite une connaissance du modèle d'objet C ++. Et il est préférable d'expliquer le sujet clairement dans un article et non dans une zone de commentaire.
La meilleure explication lisible que j'ai trouvée qui a résolu tous mes doutes à ce sujet était cet article: http://www.phpcompiler.org/articles/virtualinheritance.html
Vous n'aurez vraiment pas besoin de lire autre chose sur le sujet (sauf si vous êtes un auteur de compilateur) après avoir lu cela ...
la source
Je pense que vous confondez deux choses très différentes. L'héritage virtuel n'est pas la même chose qu'une classe abstraite. L'héritage virtuel modifie le comportement des appels de fonction; parfois, il résout les appels de fonction qui seraient autrement ambigus, parfois il reporte la gestion des appels de fonction à une classe autre que celle à laquelle on s'attend dans un héritage non virtuel.
la source
Je voudrais ajouter aux aimables clarifications d'OJ.
L'héritage virtuel n'a pas de prix. Comme pour tout ce qui est virtuel, vous obtenez un succès de performance. Il existe un moyen de contourner ce succès de performance qui est peut-être moins élégant.
Au lieu de casser le diamant en dérivant virtuellement, vous pouvez ajouter une autre couche au diamant, pour obtenir quelque chose comme ceci:
Aucune des classes n'hérite virtuellement, toutes héritent publiquement. Les classes D21 et D22 masqueront alors la fonction virtuelle f () qui est ambiguë pour DD, peut-être en déclarant la fonction privée. Ils définiraient chacun une fonction wrapper, f1 () et f2 () respectivement, chacun appelant classe (privé) f (), résolvant ainsi les conflits. La classe DD appelle f1 () si elle veut D11 :: f () et f2 () si elle veut D12 :: f (). Si vous définissez les wrappers en ligne, vous n'aurez probablement aucune surcharge.
Bien sûr, si vous pouvez changer D11 et D12, vous pouvez faire la même astuce à l'intérieur de ces classes, mais ce n'est souvent pas le cas.
la source
En plus de ce qui a déjà été dit sur les héritages multiples et virtuels, il existe un article très intéressant sur le Journal du Dr Dobb: l' héritage multiple considéré comme utile
la source
Vous êtes un peu déroutant. Je ne sais pas si vous mélangez certains concepts.
Vous n'avez pas de classe de base virtuelle dans votre OP. Vous avez juste une classe de base.
Vous avez fait l'héritage virtuel. Ceci est généralement utilisé dans l'héritage multiple afin que plusieurs classes dérivées utilisent les membres de la classe de base sans les reproduire.
Une classe de base avec une fonction virtuelle pure n'est pas instanciée. cela nécessite la syntaxe à laquelle Paul arrive. Il est généralement utilisé pour que les classes dérivées doivent définir ces fonctions.
Je ne veux plus expliquer cela parce que je ne comprends pas totalement ce que vous demandez.
la source
Cela signifie qu'un appel à une fonction virtuelle sera transféré à la "bonne" classe.
FAQ C ++ Lite FTW.
En bref, il est souvent utilisé dans des scénarios à héritage multiple, où une hiérarchie "en losange" est formée. L'héritage virtuel rompra alors l'ambiguïté créée dans la classe inférieure, lorsque vous appelez une fonction dans cette classe et que la fonction doit être résolue en classe D1 ou D2 au-dessus de cette classe inférieure. Voir l' article FAQ pour un diagramme et des détails.
Il est également utilisé dans la délégation sœur , une fonctionnalité puissante (mais pas pour les faibles de cœur). Voir cette FAQ.
Voir également l'article 40 de la 3e édition de C ++ effectif (43 dans la 2e édition).
la source
Exemple d'utilisation exécutable d'héritage Diamond
Cet exemple montre comment utiliser une classe de base virtuelle dans le scénario typique: pour résoudre l'héritage de diamant.
la source
assert(A::aDefault == 0);
de la fonction principale me donne une erreur de compilation: enaDefault is not a member of A
utilisant gcc 5.4.0. Que faut-il faire?Les classes virtuelles ne sont pas identiques à l'héritage virtuel. Des classes virtuelles que vous ne pouvez pas instancier, l'héritage virtuel est tout autre chose.
Wikipedia le décrit mieux que moi. http://en.wikipedia.org/wiki/Virtual_inheritance
la source
Héritage régulier
Avec l'héritage d'héritage non virtuel non diamant à 3 niveaux, lorsque vous instanciez un nouvel objet le plus dérivé, new est appelé et la taille requise pour l'objet est résolue à partir du type de classe par le compilateur et passée à new.
nouveau a une signature:
Et fait un appel à
malloc
, renvoyant le pointeur videIl est ensuite transmis au constructeur de l'objet le plus dérivé, qui appellera immédiatement le constructeur du milieu, puis le constructeur du milieu appellera immédiatement le constructeur de base. La base stocke ensuite un pointeur sur sa table virtuelle au début de l'objet, puis ses attributs après celui-ci. Cela revient ensuite au constructeur du milieu qui stockera son pointeur de table virtuelle au même emplacement, puis ses attributs après les attributs qui auraient été stockés par le constructeur de base. Il retourne au constructeur le plus dérivé, qui stocke un pointeur sur sa table virtuelle au même emplacement, puis ses attributs après les attributs qui auraient été stockés par le constructeur du milieu.
Parce que le pointeur de table virtuelle est remplacé, le pointeur de table virtuelle finit toujours par être la classe la plus dérivée. La virtualité se propage vers la classe la plus dérivée, donc si une fonction est virtuelle dans la classe moyenne, elle sera virtuelle dans la classe la plus dérivée mais pas dans la classe de base. Si vous transformez de manière polymorphe une instance de la classe la plus dérivée en un pointeur vers la classe de base, le compilateur ne résoudra pas cela en un appel indirect à la table virtuelle et appellera à la place la fonction directement
A::function()
. Si une fonction est virtuelle pour le type auquel vous l'avez convertie, elle se résoudra en appel dans la table virtuelle qui sera toujours celle de la classe la plus dérivée. S'il n'est pas virtuel pour ce type, il ne fera qu'appelerType::function()
et lui passer le pointeur d'objet, transtypé en Type.En fait, quand je dis pointeur sur sa table virtuelle, c'est en fait toujours un décalage de 16 dans la table virtuelle.
virtual
n'est plus requis dans les classes plus dérivées s'il est virtuel dans une classe moins dérivée car il se propage. Mais il peut être utilisé pour montrer que la fonction est bien une fonction virtuelle, sans avoir à vérifier les classes dont elle hérite des définitions de type.override
est un autre garde du compilateur qui dit que cette fonction est en train de remplacer quelque chose et si ce n'est pas le cas, alors lancez une erreur du compilateur.= 0
signifie qu'il s'agit d'une fonction abstraitefinal
empêche une fonction virtuelle d'être implémentée à nouveau dans une classe plus dérivée et s'assurera que la table virtuelle de la classe la plus dérivée contient la fonction finale de cette classe.= default
rend explicite dans la documentation que le compilateur utilisera l'implémentation par défaut= delete
donner une erreur de compilation si un appel à ceci est tentéHéritage virtuel
Considérer
Sans hériter virtuellement de la classe de basse, vous obtiendrez un objet qui ressemble à ceci:
Au lieu de cela:
C'est-à-dire qu'il y aura 2 objets de base.
Dans la situation de l' héritage de diamant virtuel ci - dessus, après nouvelle est appelée, elle appelle le constructeur le plus dérivé et dans ce constructeur, il appelle tous les trois constructeurs dérivés passant des décalages dans sa table de table virtuelle, au lieu d'appeler simplement appeler
DerivedClass1::DerivedClass1()
etDerivedClass2::DerivedClass2()
puis les deux appelsBase::Base()
Ce qui suit est compilé en mode de débogage -O0 donc il y aura un assemblage redondant
Il appelle
Base::Base()
avec un pointeur sur le décalage d'objet 32. Base stocke un pointeur sur sa table virtuelle à l'adresse qu'il reçoit et ses membres après celle-ci.DerivedDerivedClass::DerivedDerivedClass()
appelle ensuiteDerivedClass1::DerivedClass1()
avec un pointeur sur le décalage d'objet 0 et passe également l'adresse deVTT for DerivedDerivedClass+8
DerivedDerivedClass::DerivedDerivedClass()
passe ensuite l'adresse de l'objet + 16 et l'adresse de VTT pourDerivedDerivedClass+24
àDerivedClass2::DerivedClass2()
qui l'assemblage est identique à l'DerivedClass1::DerivedClass1()
exception de la lignemov DWORD PTR [rax+8], 3
qui a évidemment un 4 au lieu de 3 pourd = 4
.Après cela, il remplace les 3 pointeurs de table virtuelle de l'objet par des pointeurs vers les décalages dans
DerivedDerivedClass
la table de la représentation de cette classe.d->VirtualFunction();
:d->DerivedCommonFunction();
:d->DerivedCommonFunction2();
:d->DerivedDerivedCommonFunction();
:((DerivedClass2*)d)->DerivedCommonFunction2();
:((Base*)d)->VirtualFunction();
:la source