Guru Of the Week n° 38 : héritage multiple - deuxième partie

| Herb Sutter | Traduction par l'équipe de la rubrique C++ | gbdivers |

Difficulté : 8 / 10
Si vous ne pouvez pas utiliser l'héritage multiple, comment l'émuler ? N'oubliez pas d'émuler une syntaxe aussi naturelle que possible pour le code du client.

Retrouvez l'ensemble des Guru of the Week sur la page d'index.

N'hésitez pas à commenter cet article ! Commentez

- Titre : Guru Of the Week n° 38 : héritage multiple - deuxième partie

- Auteurs : Herb Sutter, Traduction par l'équipe de la rubrique C++, gbdivers

- Parution : 19 juillet 2012

- Licence : Copyright ® 2009 Herb Sutter. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Problème

- II. Solution

- III. Remerciements

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 II.

Merci à Luc Hermitte pour sa relecture technique et à _Max_ pour sa relecture orthographique.