Un meilleur job mieux payé ?

Deviens chef de projet, développeur, ingénieur, informaticien

Mets à jour ton profil pro

ça m'intéresse

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

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 ! 1 commentaire Donner une note à l'article (5)

Article lu   fois.

Les trois auteurs

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Problème

Considérons l'exemple suivant :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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.