7
votes

Pourquoi y a-t-il une ambiguïté dans ce modèle de diamant?

#include <iostream>
using namespace std;

class A             { public: void eat(){ cout<<"A";} };
class B: public A   { public: void eat(){ cout<<"B";} };
class C: public A   { public: void eat(){ cout<<"C";} };
class D: public B,C { public: void eat(){ cout<<"D";} };

int main(){
    A *a = new D();
    a->eat();
}
I am not sure this is called diamond problem or not, but why doesn't this work?I have given the defination for eat() for D. So, it doesn't need to use either B's or C's copy (so, there should be no problem).When I said, a->eat() (remember eat() is not virtual), there is only one possible eat() to call, that of A. Why then, do I get this error:
  'A' is an ambiguous base of 'D'
What exactly does A *a = new D(); mean to the compiler??andWhy does the same problem not occur when I use D *d = new D();?

6 Réponses :


6
votes

Le diamant entraîne deux instances d'A dans l'objet D, et il est ambigu que vous vous référez à - vous devez utiliser un héritage virtuel pour résoudre ce problème: xxx

supposant que Vous ne vouliez réellement qu'un cas. Je suppose également que vous vouliez vraiment dire: xxx


4 commentaires

@Neil Butterworth: "Copies de A dans l'objet final" Pourquoi voudriez-je copier un dans l'objet final?


@cambr J'utilisais "Copier" pour signifier "instance" - je l'ai changé.


@Neil Butterworth: En outre, j'ai défini mangez () explicitement dans D. Donc, il n'a donc pas besoin d'utiliser des copies / une instance de B ou c .


@cambr Cela n'a rien à voir avec la fonction mangeur () - essayez de commenter l'appel à celui-ci.



0
votes

L'erreur que vous obtenez ne vient pas d'appeler manger () - il vient de la ligne avant. C'est l'Upcast lui-même qui crée l'ambiguïté. Comme le souligne Neil Butterworth, il existe deux copies de A dans votre d et le compilateur ne sait pas lequel vous voulez a à pointez à.


2 commentaires

@Mike: Pourquoi ai-je des copies d'un dans votre d . J'ai défini un séparé () pour D.


Ce n'est pas à faire avec manger () du tout; L'appel a-> manger () ne se soucie pas de ce que les classes sont dérivées de a (comme vous dites, mangez est non virtuel ) - il appellera toujours a :: mangez . Comme @Neil dit, supprimez l'appel sur manger et vous avez toujours le même problème.



2
votes

Notez que l'erreur de compile est sur le "A * A = Nouveau D ();" ligne, pas sur l'appel à "manger".

Le problème est que parce que vous avez utilisé l'héritage non virtuel, vous vous retrouvez avec la classe A deux fois: une fois par B, et une fois par exemple si, par exemple, vous ajoutez un membre M à A, puis D deux d'entre eux: B :: m, et c :: m.

Parfois, vous voulez vraiment avoir une deux fois dans le graphique de dérivation, auquel cas vous devez toujours indiquer lequel vous parlez. Dans D, vous seriez capable de faire référence à B :: M et C :: m séparément.

Parfois, cependant, vous ne voulez vraiment que celui A, auquel cas vous devez utiliser Héritage virtuel .


0 commentaires

2
votes

Imaginez un scénario légèrement différent

class A             { public: void eat(){ cout<<"A";} };
class B: public A   { public: void eat(){ cout<<"B";} };
class C: public A   { public: void eat(){ cout<<"C";} };
class D: public B, public C { public: void eat(){ cout<<"D";} };

int main(){
    A *a = static_cast<B*>(new D());
      // A *a = static_cast<C*>(new D());
    a->eat();
}


5 commentaires

@ Johannes Schaub - Litb: D'accord, je comprends ce que vous avez dit. Mais pourquoi le même problème ne se produit-il pas lorsque j'utilise d * d = nouveau d ();


@CAMBR, nom de la recherche s'arrête lorsque vous faites d-> manger () dans la portée de d car il trouve mangez dans D . Il ne touchera pas le manger dans A , b ou c et n'essaierai pas de les appeler. Donc, il n'y a pas de conversion sur A fait dans ce cas, et aucune ambiguïté ne soulève donc.


Si vous essayez d-> a :: mangez () en essayant d'appeler manger sur A , vous obtiendrez le même problème depuis Il essaiera de convertir d * jusqu'à un a * .


Donc, whn i a * a = nouveau d (); un nouvel objet est créé (de type d ), puis un pointeur de type A stocke l'adresse de cet objet. Y a-t-il un problème encore? Supposons que je n'appelle pas manger () , c'est tout ce qui se passe (création d'un objet et d'une indication de pointeur). Alors, où est le problème dans ces deux étapes?


@combr, un pointeur de type a * fait pas stocke l'adresse de cet objet, mais l'adresse de l'un des objets A . L'ambiguït découle comme choix des objets à pointer.



2
votes

Pour une situation vraiment inhabituelle, la réponse de Neil est en fait faux (au moins en partie).

avec héritage virtuel , vous obtenez deux copies distinctes de A dans l'objet final.

"the Diamond" donne une copie unique de A dans l'objet final et est produit par à l'aide de l'héritage virtuel:

text alt

Etant donné que "le diamant" signifie qu'il n'y a que un copie de a dans l'objet final, une référence à A ne produit aucune ambiguïté. Sans héritage virtuel, une référence à A pourrait faire référence à deux objets différents (celui de gauche ou celui de droite dans le diagramme).


4 commentaires

Vous avez dessiné un diagramme d'objet - ma "mauvaise" réponse faisait référence au graphique d'héritage, qui est un diamant.


@Jerry Coffin: Pourquoi le même problème ne se produit-il pas lorsque j'utilise d * d = nouveau d (); ? Dans ce cas également, un objet de type d est en cours de création et les mêmes problèmes devraient se produire.


@CAMBR: Parce que a * a = nouveau d (); ne peut pas choisir le sous-objet A Avec d * d = nouveau d (); , il va pointer sur l'objet d pas l'un des deux a sujets, donc il y a donc aucune question sur l'endroit où elle devrait pointer.


+1 Pour le diagramme agréable, je pense que c'est la meilleure façon de show les deux A au cas où il n'y a pas de virtuel héritage.



0
votes

vous voulez: (réalisable avec héritage virtuel)

D / \
B c
\ /
Un

et non: (que se passe-t-il sans héritage virtuel)

D / \
B c
| |
Un

Héritage virtuel signifie qu'il n'y aura qu'une instance de la classe A de la classe non 2.

Votre type d aurait 2 points-virgents (vous pouvez les voir dans le premier diagramme), un pour B et un pour C Qui héritez pratiquement A . d La taille de l'objet est augmenté car il stocke 2 pointeurs maintenant; Cependant, il n'y a qu'un seul A maintenant.

SO B :: A et C :: A sont identiques et il ne peut donc pas y avoir d'appels ambiguës de d . Si vous n'utilisez pas d'héritage virtuel, vous avez le deuxième diagramme ci-dessus. Et tout appel à un membre d'A puis devient ambigu et vous devez spécifier quel chemin vous souhaitez prendre.

Wikipedia a une autre bonne exécution et exemple ici


0 commentaires