web-dev-qa-db-fra.com

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

Il existe deux classes de base ayant le même nom de fonction. Je veux hériter des deux, et dépasser chaque méthode différemment. Comment puis-je faire cela avec une déclaration et une définition distinctes (au lieu de définir dans la définition de 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 trouvé que le Microsoft Specific Keyword __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 existe-t-il une autre façon de faire quelque chose de plus général qui fonctionnera dans d'autres compilateurs?

56
Gohan

Ce problème ne revient 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 Design & Evolution of C++ section 12.8 et The C++ Programming Language section 25.6 ). Selon la discussion dans Design & Evolution, il y avait une proposition pour gérer ce cas spécifique avec élégance, mais elle a été rejetée parce que "de tels conflits de noms étaient peu susceptibles de devenir assez communs pour justifier une fonctionnalité de langue distincte". et "peu susceptible de devenir un travail quotidien pour les novices".

Non seulement vous avez besoin d'appeler Name() via des pointeurs vers les classes de base, vous avez besoin d'un moyen de dire whichName() que vous voulez lorsque vous travaillez sur 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();
}

Pas joli, mais la décision était que ce n'était pas souvent nécessaire.

67
Max Lybbert

Vous ne pouvez pas les remplacer séparément, vous devez les remplacer simultanément:

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 la substitution individuelle avec des 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 incorrectement reinterpret_cast, vous devriez avoir:

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 qui pourrait être ce que vous voulez (ou non):

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 la répartition virtuelle, par exemple rc.Name()), vous devez d'abord sélectionner une base. L'auto-macro est un moyen facile de nettoyer les transtypages CRTP (généralement l'accès des membres est beaucoup plus courant dans la base CRTP), mais il peut être amélioré . Il y a une brève discussion sur l'envoi virtuel dans l'une de mes autres réponses , mais sûrement meilleure si quelqu'un a un lien.

6
Roger Pate

J'ai dû faire quelque chose comme ça dans le passé, bien que dans mon cas, j'avais besoin d'hériter d'une interface deux fois et de pouvoir différencier les appels effectués sur chacun d'eux, j'ai utilisé un modèle de cale pour m'aider ...

Quelque chose comme ça:

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

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

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

Vous dérivez ensuite de InterfaceHelper deux fois plutôt que de MyInterface deux fois et vous spécifiez un id différent pour chaque classe de base. Vous pouvez ensuite distribuer deux interfaces indépendamment en effectuant une conversion vers le InterfaceHelper correct.

Vous pourriez faire quelque chose d'un peu 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 vu de compilateur ...

6
Len Holgate
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
{

};
3
Jagannath

Il y a deux autres questions connexes qui demandent des choses presque (mais pas complètement) identiques:

sélection à partir des noms de méthodes partagées héritées . Si vous voulez avoir rc.name () appelez ic1-> name () ou ic2-> name ().

Remplacement des noms de méthode partagés des classes de base (basées sur un modèle) . Cela a une syntaxe plus simple et moins de code que votre solution acceptée, mais ne le fait pas permet d'accéder aux fonctions de la classe dérivée. Plus ou moins, sauf si vous devez être en mesure d'appeler name_i1 () à partir d'un rc, vous n'avez pas besoin d'utiliser des choses comme InterfaceHelper.

1
Narfanator