59 votes

Hériter des interfaces qui partagent un nom de méthode

Deux classes de base ont le même nom de fonction. Je veux hériter des deux, et surcharger chaque méthode différemment. Comment puis-je faire cela avec une déclaration et une définition séparées (au lieu de définir dans la définition de la classe) ?

#include <cstdio>

class Interface1{
public:
    virtual void Name() = 0;
};

class Interface2
{
public:
    virtual void Name() = 0;
};

class RealClass: public Interface1, public Interface2
{
public:
    virtual void Interface1::Name()
    {
        printf("Interface1 OK?\n");
    }
    virtual void Interface2::Name()
    {
        printf("Interface2 OK?\n");
    }
};

int main()
{
    Interface1 *p = new RealClass();
    p->Name();
    Interface2 *q = reinterpret_cast<RealClass*>(p);
    q->Name();
}   

Je n'ai pas réussi à déplacer la définition dans VC8. J'ai découvert que le mot-clé spécifique de Microsoft __interface peut faire ce travail avec succès, code ci-dessous :

#include <cstdio>

__interface Interface1{
    virtual void Name() = 0;
};

__interface Interface2
{
    virtual void Name() = 0;
};

class RealClass: public Interface1,
                public Interface2
{
public:
    virtual void Interface1::Name();
    virtual void Interface2::Name();
};

void RealClass::Interface1::Name()
{
    printf("Interface1 OK?\n");
}

void RealClass::Interface2::Name()
{
    printf("Interface2 OK?\n");
}

int main()
{
    Interface1 *p = new RealClass();
    p->Name();
    Interface2 *q = reinterpret_cast<RealClass*>(p);
    q->Name();
}  

mais y a-t-il une autre façon de faire quelque chose de plus général qui fonctionnerait avec d'autres compilateurs ?

71voto

Max Lybbert Points 11822

Ce problème ne se pose pas très souvent. La solution que je connais a été conçue par Doug McIlroy et apparaît dans les livres de Bjarne Stroustrup (présentés à la fois en Conception et évolution de C++ l'article 12.8 et Le langage de programmation C++ section 25.6). Selon la discussion dans Conception et évolution Il y a eu une proposition pour traiter ce cas spécifique de manière élégante, mais elle a été rejetée parce que "de tels conflits de noms n'étaient pas susceptibles de devenir suffisamment courants pour justifier une caractéristique de langage distincte" et "n'étaient pas susceptibles de devenir un travail quotidien pour les novices".

Vous devez non seulement appeler Name() par le biais de pointeurs vers les classes de base, vous avez besoin d'un moyen de dire qui Name() que vous souhaitez lors de l'utilisation de la classe dérivée. La solution ajoute une certaine indirection :

class Interface1{
public:
    virtual void Name() = 0;
};

class Interface2{
public:
    virtual void Name() = 0;
};

class Interface1_helper : public Interface1{
public:
    virtual void I1_Name() = 0;
    void Name() override
    {
        I1_Name();
    }
};

class Interface2_helper : public Interface2{
public:
    virtual void I2_Name() = 0;
    void Name() override
    {
        I2_Name();
    }
};

class RealClass: public Interface1_helper, public Interface2_helper{
public:
    void I1_Name() override
    {
        printf("Interface1 OK?\n");
    }
    void I2_Name() override
    {
        printf("Interface2 OK?\n");
    }
};

int main()
{
    RealClass rc;
    Interface1* i1 = &rc;
    Interface2* i2 = &rc;
    i1->Name();
    i2->Name();
    rc.I1_Name();
    rc.I2_Name();
}

Ce n'est pas très joli, mais la décision a été prise parce que ce n'est pas souvent nécessaire.

8voto

Vous ne pouvez pas les remplacer séparément, vous devez les remplacer tous les deux en même temps :

struct Interface1 {
  virtual void Name() = 0;
};

struct Interface2 {
  virtual void Name() = 0;
};

struct RealClass : Interface1, Interface2 {
  virtual void Name();
};
// and move it out of the class definition just like any other method:
void RealClass::Name() {
  printf("Interface1 OK?\n");
  printf("Interface2 OK?\n");
}

Vous pouvez simuler une surcharge individuelle à l'aide de classes de base intermédiaires :

struct RealClass1 : Interface1 {
  virtual void Name() {
    printf("Interface1 OK?\n");
  }
};

struct RealClass2 : Interface2 {
  virtual void Name() {
    printf("Interface2 OK?\n");
  }
};

struct RealClass : RealClass1, RealClass2 {
  virtual void Name() {
    // you must still decide what to do here, which is likely calling both:
    RealClass1::Name();
    RealClass2::Name();

    // or doing something else entirely

    // but note: this is the function which will be called in all cases
    // of *virtual dispatch* (for instances of this class), as it is the
    // final overrider, the above separate definition is merely
    // code-organization convenience
  }
};

De plus, vous utilisez reinterpret_cast de manière incorrecte, vous auriez dû le faire :

int main() {
  RealClass rc; // no need for dynamic allocation in this example

  Interface1& one = rc;
  one.Name();

  Interface2& two = dynamic_cast<Interface2&>(one);
  two.Name();

  return 0;
}

Et voici une réécriture avec CRTP c'est peut-être ce que vous voulez (ou pas) :

template<class Derived>
struct RealClass1 : Interface1 {
#define self (*static_cast<Derived*>(this))
  virtual void Name() {
    printf("Interface1 for %s\n", self.name.c_str());
  }
#undef self
};

template<class Derived>
struct RealClass2 : Interface2 {
#define self (*static_cast<Derived*>(this))
  virtual void Name() {
    printf("Interface2 for %s\n", self.name.c_str());
  }
#undef self
};

struct RealClass : RealClass1<RealClass>, RealClass2<RealClass> {
  std::string name;
  RealClass() : name("real code would have members you need to access") {}
};

Mais notez qu'ici vous ne pouvez pas appeler Name sur une RealClass maintenant (avec le dispatching virtuel, par exemple). rc.Name() ), vous devez d'abord sélectionner une base. La macro self est un moyen facile de nettoyer les castings de la CRTP (l'accès des membres est généralement beaucoup plus fréquent dans la base de la CRTP), mais il peut être difficile d'y accéder. améliorée . Il y a une brève discussion sur le dispatching virtuel dans l'un de mes articles. autres réponses mais il en existe sûrement un meilleur si quelqu'un a un lien.

6voto

Len Holgate Points 12579

J'ai déjà eu à faire quelque chose comme ça dans le passé, mais dans mon cas j'avais besoin d'hériter d'une interface deux fois et d'être capable de différencier les appels faits sur chacune d'entre elles, j'ai utilisé un template shim pour m'aider...

Voici ce qu'il en est :

template<class id>
class InterfaceHelper : public MyInterface
{
    public : 

       virtual void Name() 
       {
          Name(id);
       }

       virtual void Name(
          const size_t id) = 0;  
}

Vous déduisez ensuite de InterfaceHelper deux fois plutôt que de MyInterface deux fois et que vous spécifiez un id pour chaque classe de base. Vous pouvez alors distribuer deux interfaces de manière indépendante en les coulant dans la bonne classe de base. InterfaceHelper .

Vous pourriez faire quelque chose de légèrement plus complexe ;

class InterfaceHelperBase
{
    public : 

       virtual void Name(
          const size_t id) = 0;  
}

class InterfaceHelper1 : public MyInterface, protected InterfaceHelperBase
{
    public : 

       using InterfaceHelperBase::Name;

       virtual void Name() 
       {
          Name(1);
       }
}

class InterfaceHelper2 : public MyInterface, protected InterfaceHelperBase
{
    public : 

       using InterfaceHelperBase::Name;

       virtual void Name() 
       {
          Name(2);
       }
}

class MyClass : public InterfaceHelper1, public InterfaceHelper2
{
    public :

      virtual void Name(
          const size_t id)
      {
          if (id == 1) 
          {
              printf("Interface 1 OK?");
          }
          else if (id == 2) 
          {
              printf("Interface 2 OK?");
          }
      }  
}

Notez que ce qui précède n'a pas été compilé...

3voto

Jagannath Points 2326
class BaseX
{
public:
    virtual void fun()
    {
        cout << "BaseX::fun\n";
    }
};

class BaseY
{
public:
    virtual void fun()
    {
        cout << "BaseY::fun\n";
    }
};

class DerivedX : protected BaseX
{
public:
    virtual void funX()
    {
        BaseX::fun();
    }
};

class DerivedY : protected BaseY
{
public:
    virtual void funY()
    {
        BaseY::fun();
    }
};

class DerivedXY : public DerivedX, public DerivedY
{

};

1voto

Narfanator Points 1550

Deux autres questions connexes posent des problèmes presque identiques (mais pas complètement) :

Sélection de noms de méthodes partagées héritées . Si vous voulez que rc.name() appelle ic1->name() ou ic2->nom().

Surcharge des noms de méthodes partagées des classes de base (modélisées) . Cette solution a une syntaxe plus simple et moins de code que la solution que vous avez acceptée, mais n'est pas permettent d'accéder aux fonctions de la classe dérivée. Plus ou moins, à moins que vous n'ayez besoin de pouvoir appeler name_i1() à partir d'une rc, vous n'avez pas besoin d'utiliser des choses comme InterfaceHelper.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X