Reprise d'implémentation de méthodes virtuelles pures à partir d'une sous-classe

a marqué ce sujet comme résolu.

Bonjour,

J’ai un petit problème certainement très basique que je ne comprends pas.

J’ai une interface I1, et une seconde interface I2 qui étend I1. J’ai une class C1 qui implémente I1, et une classe C2 qui étend C1 et qui implémente I2. C’est très très simple:

struct I1 {
  virtual void foo () = 0;
};

struct I2: I1 {
  virtual void bar () = 0;
};

struct C1: I1 {
  void foo () override {
    std::cout << "Foo from I1" << std::endl;
  }
};

struct C2: C1, I2 {
  void bar () override {
    std::cout << "Bar from I2" << std::endl;
  }
};

int main (int argc, char** argv) {
  I2* i = new C2();
  i->foo();
  i->bar();
  return 0;
}

Voici l’erreur que j’obtiens:

test713.cpp: In function 'int main(int, char**)':
test713.cpp:26:16: error: invalid new-expression of abstract class type 'C2'
 I2* i = new C2();
                ^
test713.cpp:17:8: note:   because the following virtual functions are pure within 'C2':
 struct C2: C1, I2 {
        ^~
test713.cpp:4:14: note: 	'virtual void I1::foo()'
 virtual void foo () = 0;
              ^~~

Il prétend ne pas pouvoir instancier mon objet C2 car la méthode virtuelle pure foo n’est pas définie. Pourtant, cette implémentation existe, puisque C2 étant C1 et C1 en définnit bien une implémentation.

A tout hasard j’ai essayé d’ajouter:

using C1::foo;

J’ai aussi essayé de changer l’ordre d’héritage:

struct C2: I2, C1 {

Mais ça ne change rien.

Si je redéfinis explicitement foo dans C2, ça fonctionne:

void foo () override {
  C1::foo();
}

Pourquoi l’implémentation existante de C1::foo n’est pas automatiquement reprise dans C2 ?

Comment lui dire de réutiliser c1::foo sans la redéfinir explicitement ?

Dans mon cas concret, je ne n’ai pas le contrôle sur I1 et C1 qui font partie d’un framework (wxWidgets), et I1 définit plusieurs dizaines (si pas centaines) de méthodes virtuelles pures que C1 implémente déjà très bien. Dans C2, je redéfinis quelques-unes des méthodes de C1, mais je ne touche pas à la plupart d’entre-elles. J’aimerais bien éviter d’écrire du wrapper code parfaitement inutile.

Comment faire ?

Merci pour vos réponses.

+0 -0

Bonjour QuentinC,

Les interfaces n’existent pas en C++ (contrairement au Java par exemple). Tu hérites 2 fois de I1, séparément. Quand le compilateur te dit que I1::foo() n’est pas définie, en réalité c’est de I2::foo() dont il parle.

Si tu pouvais modifier C1, tu aurais la possibilité de faire appel à l’héritage en diamant via un héritage virtuel pour solutionner techniquement le problème.

Mais comme ce n’est pas le cas, tu ne pourras de toute façon pas le faire ; et heureusement il y a d’autres solutions.

La première, c’est les templates :

#include <iostream>

struct I1 {
  virtual void foo () = 0;
};

template<class Base>
struct I2: Base {
  virtual void bar () = 0;
};

struct C1: I1 {
  void foo () override {
    std::cout << "Foo from I1" << std::endl;
  }
};

struct C2: I2<C1> {
  void bar () override {
    std::cout << "Bar from I2" << std::endl;
  }
};

int main (int argc, char** argv) {
  I2<C1>* i = new C2();
  i->foo();
  i->bar();
  return 0;
}

La 2e, c’est d’ajouter un attribut à ta classe.

#include <iostream>

struct I1 {
  virtual void foo () = 0;
};

struct I2 {
  I2(I1& i1): i1(i1) {}
  virtual void bar () = 0;
  I1& i1;
};

struct C1: I1 {
  void foo () override {
    std::cout << "Foo from I1" << std::endl;
  }
};

struct C2: C1, I2 {
  C2():I2(static_cast<I1&>(*this)){}
  void bar () override {
    std::cout << "Bar from I2" << std::endl;
  }
};

int main (int argc, char** argv) {
  I2* i = new C2();
  i->i1.foo();
  i->bar();
  return 0;
}

Mais le mieux est probablement d’aller voir quel est la méthode habituelle pour étendre des classes dans WxWidget (que je ne connais pas)…

+0 -0

Bonjour,

Java étant effectivement mon langage de prédilection, je n’avais effectivement pas pensé qu’en C++, ça donnait lieu à deux héritages complètement séparés. Du coup on comprend pourquoi ce n’est pas implicite, le compilateur ne peut ou ne veut pas décider.

C’est d’autant plus rageant qu’il n’y a même pas de décalage de pointeur this. Même dans mon cas concret, I1 est bien une interface à la Java, elle ne définit aucun membre. IL n’y aurait aucun problème à bêtement recopier les pointeurs de fonction de la vtable, je pense…

La première solution a l’air super intéressante et bien trouvée, mais comme dans I2 j’ai aussi des méthodes concrètes (de la gestion d’évènements par exemple), et comme j’ai une autre classe C2 qui étend un autre C1 implémentant aussi I1, ça m’oblige à faire un truc pas très confortable. Vu que c’est du template, je suis obligé de déplacer le code dans les headers, et puis dans l’exécutable final ça fait deux instanciations alors qu’une seule serait nécessaire (les méthodes implémentées dans I2 n’appellent que des méthodes du parent I1, pas directement des implémentations concrètes de C1A ou C1B).

Pour concrétiser cette solution là de manière optimale, il faudrait trouver un moyen de sortir toute les méthodes de I2 et les mettre ailleurs où on ne dépend pas de templates. Je ne sais pas si c’est faisable… je vais chercher.

Avec la deuxième solution, en gros, on implémente "à la mano" l’héritage virtuelle. Mais du coup c’est pas hyper pratique, il faut se souvenir de ce qui vient de I1.

A voir si je ne ferais pas mieux de faire de la composition plutôt que de l’héritage.

Mais le mieux est probablement d’aller voir quel est la méthode habituelle pour étendre des classes dans WxWidget (que je ne connais pas)…

Normalement, on peut étendre les classes de composants et ça fonctionne très bien, je l’ai déjà fait souvent. Les cas où ça ne marche pas et/ou où ce n’est pas recommandés sont bien expliqués dans la doc.

Merci en tout cas pour ta réponse.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte