Le diamant n'est pas un problème, à condition que vous ne utiliser quelque chose comme l'héritage virtuel C++ : dans l'héritage normal, chaque classe de base ressemble à un champ membre (en fait, ils sont disposés de cette manière dans RAM), ce qui vous donne un peu de sucre syntaxique et une capacité supplémentaire à surcharger plus de méthodes virtuelles. Cela peut créer une certaine ambiguïté au moment de la compilation, mais c'est généralement facile à résoudre.
En revanche, avec l'héritage virtuel, il est trop facile d'échapper à tout contrôle (et de semer le désordre). Prenons l'exemple d'un diagramme en forme de cœur :
A A
/ \ / \
B C D E
\ / \ /
F G
\ /
H
En C++, c'est tout à fait impossible : dès que F
y G
sont fusionnés en une seule classe, leur A
sont également fusionnés, point final. Cela signifie que vous ne pouvez jamais considérer les classes de base comme opaques en C++ (dans cet exemple, vous devez construire A
en H
il faut donc savoir qu'il est présent quelque part dans la hiérarchie). Dans d'autres langues, cela peut fonctionner, par exemple, F
y G
pourraient déclarer explicitement que A est "interne", interdisant ainsi les fusions qui en découlent et se solidifiant de fait.
Un autre exemple intéressant ( no spécifique au C++) :
A
/ \
B B
| |
C D
\ /
E
Ici, seuls les B
utilise l'héritage virtuel. Ainsi, le E
contient deux B
qui partagent le même A
. De cette façon, vous pouvez obtenir une A*
qui pointe vers E
mais vous ne pouvez pas le transformer en un B*
pointeur bien que l'objet est en fait B
en tant que tel est ambigu, et cette ambiguïté ne peut être détectée au moment de la compilation (à moins que le compilateur ne voie l'ensemble du programme). Voici le code de test :
struct A { virtual ~A() {} /* so that the class is polymorphic */ };
struct B: virtual A {};
struct C: B {};
struct D: B {};
struct E: C, D {};
int main() {
E data;
E *e = &data;
A *a = dynamic_cast<A *>(e); // works, A is unambiguous
// B *b = dynamic_cast<B *>(e); // doesn't compile
B *b = dynamic_cast<B *>(a); // NULL: B is ambiguous
std::cout << "E: " << e << std::endl;
std::cout << "A: " << a << std::endl;
std::cout << "B: " << b << std::endl;
// the next casts work
std::cout << "A::C::B: " << dynamic_cast<B *>(dynamic_cast<C *>(e)) << std::endl;
std::cout << "A::D::B: " << dynamic_cast<B *>(dynamic_cast<D *>(e)) << std::endl;
std::cout << "A=>C=>B: " << dynamic_cast<B *>(dynamic_cast<C *>(a)) << std::endl;
std::cout << "A=>D=>B: " << dynamic_cast<B *>(dynamic_cast<D *>(a)) << std::endl;
return 0;
}
En outre, la mise en œuvre peut être très complexe (cela dépend de la langue ; voir la réponse de benjismith).
56 votes
Je voudrais juste mentionner que C++ est excellent pour vous donner assez de corde pour vous pendre.
1 votes
Pour une alternative à l'héritage multiple qui aborde (et, IMHO, résout) un grand nombre des mêmes problèmes, regardez les Traits ( iam.unibe.ch/~scg/Recherche/Traits )
53 votes
Je pensais que C++ vous donnait suffisamment de marge de manœuvre pour vous tirer une balle dans le pied.
6 votes
Cette question semble supposer qu'il y a un problème avec l'IM en général, alors que j'ai trouvé beaucoup de langues où l'IM est utilisé occasionnellement. Il y a certainement des problèmes avec la gestion de l'IM dans certaines langues, mais je ne suis pas au courant que l'IM en général pose des problèmes significatifs.