I. Problème▲
Considérons l'exemple suivant :
struct
A {
virtual
~
A() {
}
virtual
string Name() {
return
"A"
; }
}
;
struct
B1 : virtual
A {
string Name() {
return
"B1"
; }
}
;
struct
B2 : virtual
A {
string Name() {
return
"B2"
; }
}
;
struct
D : B1, B2 {
string Name() {
return
"D"
; }
}
;
Montrez la meilleure façon que vous puissiez trouver pour « contourner » le fait de ne pas utiliser l'héritage multiple en écrivant une classe D équivalente (ou aussi proche que possible) sans utiliser l'héritage multiple. Comment obtiendriez-vous le même effet et la même capacité d'utilisation pour D avec aussi peu de changements que possible de la syntaxe dans le code client ?
Pour démarrer : vous pouvez commencer en envisageant les cas du schéma de test suivant :
void
f1( A&
x ) {
cout <<
"f1:"
<<
x.Name() <<
endl; }
void
f2( B1&
x ) {
cout <<
"f2:"
<<
x.Name() <<
endl; }
void
f3( B2&
x ) {
cout <<
"f3:"
<<
x.Name() <<
endl; }
void
g1( A x ) {
cout <<
"g1:"
<<
x.Name() <<
endl; }
void
g2( B1 x ) {
cout <<
"g2:"
<<
x.Name() <<
endl; }
void
g3( B2 x ) {
cout <<
"g3:"
<<
x.Name() <<
endl; }
int
main() {
D d;
B1*
pb1 =
&
d; // D* -> B* conversion
B2*
pb2 =
&
d;
B1&
rb1 =
d; // D& -> B& conversion
B2&
rb2 =
d;
f1( d ); // polymorphism
f2( d );
f3( d );
g1( d ); // slicing
g2( d );
g3( d );
// dynamic_cast/RTTI
cout <<
( (dynamic_cast
<
D*>
(pb1) !=
0
) ? "ok "
: "bad "
);
cout <<
( (dynamic_cast
<
D*>
(pb2) !=
0
) ? "ok "
: "bad "
);
try
{
dynamic_cast
<
D&>
(rb1);
cout <<
"ok "
;
}
catch
(...) {
cout <<
"bad "
;
}
try
{
dynamic_cast
<
D&>
(rb2);
cout <<
"ok "
;
}
catch
(...) {
cout <<
"bad "
;
}
}
II. Solution▲
Considérons l'exemple suivant. Montrez la meilleure façon que vous puissiez trouver pour « contourner » le fait de ne pas utiliser l'héritage multiple en écrivant une classe D équivalente (ou aussi proche que possible) sans utiliser l'héritage multiple. Comment obtiendriez-vous le même effet et la même capacité d'utilisation pour D avec aussi peu de changements que possible de la syntaxe dans le code client ?
Il existe quelques stratégies, chacune avec ses points faibles, mais en voici une qui s'approche très près du but :
struct
D : B1 {
struct
D2 : B2 {
void
Set ( D*
d ) {
d_ =
d; }
string Name();
D*
d_;
}
d2_;
D() {
d2_.Set( this
); }
D( const
D&
other ) : B1( other ), d2_( other.d2_ ) {
d2_.Set( this
);
}
D&
operator
=
( const
D&
other ) {
B1::
operator
=
( other );
d2_ =
other.d2_;
return
*
this
;
}
operator
B2&
() {
return
d2_; }
B2&
AsB2() {
return
d2_; }
string Name() {
return
"D"
; }
}
;
string D::D2::
Name() {
return
d_->
Name(); }
On a pour inconvénients que :
- fournir l'opérateur B2& fait référence de façon discutable à un traitement spécial (incohérent) des pointeurs ;
- il vous faut appeler explicitement D::AsB2() pour utiliser D comme un B2 (dans le cadre du test, cela signifie changer B2* pb2 = &d; en B2* pb2 = &d.AsB2();) ;
- le dynamic_cast de D* en B2* ne marche toujours pas (il est possible de contourner ce problème si vous voulez utiliser le pré-processeur pour redéfinir les appels dynamic_cast).
Il est intéressant, et vous l'avez peut-être observé, que la conception de l'objet D en mémoire est probablement identique à ce qu'aurait donné l'héritage multiple. C'est parce que nous essayons de simuler l'héritage multiple, juste sans tout le sucre syntaxique et les commodités que pourrait fournir l'assistance de constructions incorporées au langage.
III. Remerciements▲
Cet article est une traduction en français par l'équipe de la rubrique C++ de l'article de Herb Sutter publié sur Guru of the Week. Vous pouvez retrouver cet article dans sa version originale sur le site de Guru of the Week : Multiple Inheritance - Part IIMultiple Inheritance - Part II.
Merci à Luc Hermitte pour sa relecture technique et à _Max_ pour sa relecture orthographique.