Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Heritage multiple et fonctions virtuelles

6 réponses
Avatar
plagne.laurent
Bonjour,

je voudrais compiler le code suivant et cela ne marche pas... Pourtant,
il me semble que le compilo devrait pouvoir trouver la definition de la
methode "methode" via le second heritage.... Des suggestions ?

#include <iostream>

class VirtualBase{
public:
virtual void methode( void ) = 0;
};

class ConcreteBase{
public:
void methode( void ){
std::cout << " ConcreteBase::methode called" << std::endl;
}
};


class DerivedClass : public VirtualBase, ConcreteBase{
public:
/// Je ne voudrais pas avoir a decommenter la ligne suivante...
////virtual void methode( void ){ ConcreteBase::methode();}
};

int main( int argc, char *argv[] )
{

DerivedClass toto;
toto.methode();
}

P.S. A priori, je voudrais eviter que ConcreteBase herite de VirtualBase

6 réponses

Avatar
Franck Branjonneau
"" écrivait:

il me semble que le compilo devrait pouvoir trouver la definition de la
methode "methode" via le second heritage....


Et comment si prendrait-il ?

class VirtualBase{
public:
virtual void methode( void ) = 0;
};

class ConcreteBase{
public:
void methode( void ){
std::cout << " ConcreteBase::methode called" << std::endl;
}
};


class DerivedClass : public VirtualBase, ConcreteBase{
public:
/// Je ne voudrais pas avoir a decommenter la ligne suivante...
////virtual void methode( void ){ ConcreteBase::methode();}
};


DerivedClass à deux fonctions membres « methode » : VirtualBase::methode
et ConcreteBase::methode ; sur quel(s) critère(s) le compilateur
fait-il son choix ?

P.S. A priori, je voudrais eviter que ConcreteBase herite de VirtualBase


Pourquoi ?

--
Franck Branjonneau

Avatar
James Kanze
wrote:

je voudrais compiler le code suivant et cela ne marche pas...
Pourtant, il me semble que le compilo devrait pouvoir trouver
la definition de la methode "methode" via le second
heritage....


Quel second héritage ? Est-ce que tu n'aurais pas oublié
quelques déclarations importantes ?

Des suggestions ?

#include <iostream>

class VirtualBase{
public:
virtual void methode( void ) = 0;
};


Est-ce que le nom de la classe signifie ce qu'il semblerait
signifier, où est-ce qu'il est choisi simplement pour induire en
confusion.

class ConcreteBase{
public:
void methode( void ){
std::cout << " ConcreteBase::methode called" << std::endl;
}
};


ConcreteBase::methode ne pourrait en aucun cas supplanter
VirtualBase::methode ici, puisqu'elle n'a pas VirtualBase comme
base. Est-ce que tu n'aurais pas voulu ?

class ConcreteBase : public VirtualBase { /* ... */ } ;

Voire même :

class ConcreteBase : public virtual VirtualBase { /* ... */ } ;

, pourque VirtualBase correspond bien à son nom.

Dans les deux cas, il est plus clair si tu déclares methode
virtual. Formellement, ce n'est pas nécessaire, mais si tu sais
qu'elle est virtuelle, pourquoi en cacher le fait du lecteur.

class DerivedClass : public VirtualBase, ConcreteBase{


Ici, tu hérites de deux classes distinctes, sans rapport entre
elles (telles que tu l'as écris au départ). Et donc, tu as deux
fonctions « methode » distinctes, dont une pure virtuelle, et
l'autre même pas virtuelle (et puisque l'héritage est privé,
qu'on ne peut pas appeler d'en dehors de la classe).

Si tu convertis ConcreteBase d'hériter de VirtualBase,
DerivedClass contient deux instances de VirtualBase, celle dont
il hérite directement, et celle dont il hérite à travers
ConcreteBase. Et la fonction VirtualBase::methode dans
l'instance dont il hérite directement n'est toujours pas
supplantée.

Ce dont je crois que tu veux, ici, c'est :

class DerivedClass
: public virtual VirtualBase
, private ConcreteBase
{
// ...
} ;

DerivedClass est une implémentation de VirtualBase (donc, elle
en hérite directement) ; pour l'implémentation concrète, on
utilise ConcreteBase, dont on hérite de façon privée, parce que
son utilisation ici est un détail de l'implémentation. Parce que
ConcreteBase implémente en fait VirtualBase, elle en hérite
aussi, et finalement, pour qu'il n'y a qu'une instance de
VirtualBase dans l'hièrarchie d'héritage, tous les héritages de
cette classe sont virtual.

Fait comme ça, ça marche. C'est même un modèle fréquent, dont
Barton et Nackman, par exemple, contient de bons exemples.

public:
/// Je ne voudrais pas avoir a decommenter la ligne suivante...
////virtual void methode( void ){ ConcreteBase::methode();}
};

int main( int argc, char *argv[] )
{

DerivedClass toto;
toto.methode();
}

P.S. A priori, je voudrais eviter que ConcreteBase herite de
VirtualBase


Pourquoi ? Ça me semble une contradiction. Ou bien, elle en
hérite, ou bien, elle ne peut pas en supplanter des fonctions
virtuelles ; on ne peut pas supplanter une fonction virtuelle
dont on ne connaît même pas l'existence.

--
James Kanze (Gabi Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
plagne.laurent
Bonjour, merci beaucoup pour vos lumières.
Franck Branjonneau wrote:

Et comment si prendrait-il ?



En fait, c'est un collègue qui m'a soumis le problème et je lui ais
fait ce genre de réponse (bien que ma maitrise du C++ ne soit pas
telle que je puisse être aussi affirmatif). Néanmoins, il me semblait
assez naturel que cela ne puisse pas fonctionner car cela impliquerait
un héritage assymétrique (ordonné). A cela il m'a répondu que je
lui faisait une réponse impliquant des contraintes techniques des
compilo C++ et non pas pourquoi son code (pseudo-code) violait la
sémantique de l'héritage... D'où mon post.

La suite de mes explications confuses en réponse à J. Kanze...

class VirtualBase{
public:
virtual void methode( void ) = 0;
};

class ConcreteBase{
public:
void methode( void ){
std::cout << " ConcreteBase::methode called" << std::endl;
}
};


class DerivedClass : public VirtualBase, ConcreteBase{
public:
/// Je ne voudrais pas avoir a decommenter la ligne suivante...
////virtual void methode( void ){ ConcreteBase::methode();}
};


DerivedClass à deux fonctions membres « methode » : VirtualBase::me thode
et ConcreteBase::methode ; sur quel(s) critère(s) le compilateur
fait-il son choix ?

P.S. A priori, je voudrais eviter que ConcreteBase herite de VirtualBase


Pourquoi ?

--
Franck Branjonneau



Avatar
plagne.laurent
Merci pour votre longue réponse.

James Kanze wrote:

P.S. A priori, je voudrais eviter que ConcreteBase herite de
VirtualBase


Pourquoi ? Ça me semble une contradiction. Ou bien, elle en
hérite, ou bien, elle ne peut pas en supplanter des fonctions
virtuelles ; on ne peut pas supplanter une fonction virtuelle
dont on ne connaît même pas l'existence.



Le problème est que dans le cas réel la classe abstraite VirtualBase
est définie par un tiers (CORBA) et oblige le développeur de la
classe d'implémentation à définir un GRAND nombre de méthodes (pas
seulement une). De là l'idée de mon collègue d'utiliser plusieurs
classes d'implémentation (ConcreteBase1 ConcreteBase2) relatives à
des méthodes individuelles de VirtualBase (elles ne peuvent donc pas
hériter de la classe abstraite). La classe utilisateur (DerivedClass)
hérite d'une part des contraintes définies par la classe abstraite et
d'autre part des implementations définies par les classes
ConcreteBase1, ConcreteBase2...

Un mécanisme de délégation vers les classes d'implémentation semble
plus approprié dans ce cas, mais dans tous les cas, on n'est obligé
d'écrire 2 fois chacunes des méthodes imposées par la classe
abstraite :

struct VirtualBase{
void m1() =0;
void m2()=0;
};

struct ConcreteBase1{
void m1{...}
};

struct ConcreteBase2{
void m2{...}
};

class Derived : public VirtualBase, private ConcreteBase1, private
ConcreteBase2{
public:
void m1(){ ConcreteBase1::m1())};
void m2(){ ConcreteBase2::m2())};
};

OU (je préfère..)

class Derived : public VirtualBase{
public:
void m1(){ c1_.m1())};
void m2(){ c2_.m2())};
private:
ConcreteBase1 c1_;
ConcreteBase2 c2_;
};

Ce qui chagrine mon collègue, c'est d'avoir à définir m1 et m2 deux
fois (même si la définition dans la classe dérivée
me semble être une aide pour le lecteur).

Plus clair ou de + en + à coté de la plaque ?

Laurent


Avatar
Michel Decima

Le problème est que dans le cas réel la classe abstraite VirtualBase
est définie par un tiers (CORBA) et oblige le développeur de la
classe d'implémentation à définir un GRAND nombre de méthodes (pas
seulement une). De là l'idée de mon collègue d'utiliser plusieurs
classes d'implémentation (ConcreteBase1 ConcreteBase2) relatives à
des méthodes individuelles de VirtualBase (elles ne peuvent donc pas
hériter de la classe abstraite). La classe utilisateur (DerivedClass)
hérite d'une part des contraintes définies par la classe abstraite et
d'autre part des implementations définies par les classes
ConcreteBase1, ConcreteBase2...

Un mécanisme de délégation vers les classes d'implémentation semble
plus approprié dans ce cas, mais dans tous les cas, on n'est obligé
d'écrire 2 fois chacunes des méthodes imposées par la classe
abstraite :

Ce qui chagrine mon collègue, c'est d'avoir à définir m1 et m2 deux
fois (même si la définition dans la classe dérivée
me semble être une aide pour le lecteur).


Avec l'heritage virtuel, tu peux te passer de redefinir m1 et m2 dans
la classe Derived (pas besoin d'appel explicite a la classe parente ni
de delegation) :

struct VirtualBase{
virtual void m1() =0;
virtual void m2()=0;
};

struct ConcreteBase1 : public virtual VirtualBase {
void m1() { std::cout << "ConcreteBase1::m1n"; }
};

struct ConcreteBase2 : public virtual VirtualBase {
void m2() { std::cout << "ConcreteBase2::m2n"; }
};

class Derived
: public virtual VirtualBase
, public ConcreteBase1
, public ConcreteBase2{
public:
};

int main() {
Derived d;
d.m1();
d.m2();
}

Plus clair ou de + en + à coté de la plaque ?


tres clair.

Avatar
kanze
wrote:
Merci pour votre longue réponse.
James Kanze wrote:

P.S. A priori, je voudrais eviter que ConcreteBase herite de
VirtualBase


Pourquoi ? Ça me semble une contradiction. Ou bien, elle en
hérite, ou bien, elle ne peut pas en supplanter des
fonctions virtuelles ; on ne peut pas supplanter une
fonction virtuelle dont on ne connaît même pas l'existence.


Le problème est que dans le cas réel la classe abstraite
VirtualBase est définie par un tiers (CORBA) et oblige le
développeur de la classe d'implémentation à définir un GRAND
nombre de méthodes (pas seulement une). De là l'idée de mon
collègue d'utiliser plusieurs classes d'implémentation
(ConcreteBase1 ConcreteBase2) relatives à des méthodes
individuelles de VirtualBase (elles ne peuvent donc pas
hériter de la classe abstraite).


C'est l'idiome de mixin classique. Pourquoi est-ce que les
classes d'implémentation partielles ne peuvent-elles pas hériter
de VirtualBase ? Elles le font dans l'idiome classique.

En fait, quand j'utilise cet idiome, j'ai une tendance à
introduire des classes abstraites intermédiaires, une pour
chaque regroupement fonctionnel, et que mes classes
d'implémentation héritent d'elles. Quelque chose du genre :

class BaseA : public virtual VirtualBase
{
public:
virtual void fonctionA() = 0 ;
} ;

class BaseB : public virtual VirtualBase
{
virtual void fonctionB() = 0 ;
} ;

class ImplA1 : public BaseA
{
public:
virtual void fonctionA() ;
} ;

class ImplA2 : public BaseA
{
public:
virtual void fonctionA() ;
} ;

// ...

Ces classes intermédiaires ne servent strictement à rien en ce
qui concerne le langage ; elles ne font que repéter une partie
de l'interface abstraite de la classe de base. Mais je trouve
par la suite que c'est plus clair que ImplA1 implémente les
fonctions dans le regroupement A si elle hérite de BaseA, plutôt
que si elle hérite de VirtualBase ; le rôle de chaque mixin
(c'est le nom qu'on donne à ces classes d'implémentation
partielle) me semble plus clair.

Ensuite, la classe finale pourrait même être un template :

template< typename ImplA, typename ImplB >
class Concrete
: public virtual VirtualBase
, public ImplA
, public ImplB
{
// éventuellement, il faudrait des constructeurs,
// mais c'est tout.
} ;

typedef Concrete< ImplA1, ImplB1 > ConcreteA1B1 ;
// ...

Je ne peux pas dire m'en servir souvent, mais c'est un idiome
qui m'a renu de bons services une ou deux fois par la passée.

La classe utilisateur (DerivedClass) hérite d'une part des
contraintes définies par la classe abstraite et d'autre part
des implementations définies par les classes ConcreteBase1,
ConcreteBase2...


Certes, mais quel rapport ?

Un mécanisme de délégation vers les classes d'implémentation
semble plus approprié dans ce cas, mais dans tous les cas, on
n'est obligé d'écrire 2 fois chacunes des méthodes imposées
par la classe abstraite :


La délégation est aussi une possibilité -- la aussi,
l'utilisation d'un template pour la classe finale peut
économiser pas mal d'écriture de code. La délégation a
l'avantage de réduire le couplage entre les différentes
implémentations, mais a aussi le désavantage qu'une
implémentation d'une fonction dans le regroupement A ne peut pas
se servir d'une fonction dans le regroupement B ; dans au moins
un des cas où je me suis servi de cet idiome, des fonctions
d'implémentation dans le regroupement A appelaient bien des
fonctions virtuelles de VirtualBase qui se trouvaient
implémentée dans le regroupement B.

struct VirtualBase{
void m1() =0;
void m2()=0;
};

struct ConcreteBase1{
void m1{...}
};

struct ConcreteBase2{
void m2{...}
};

class Derived : public VirtualBase, private ConcreteBase1, private
ConcreteBase2{
public:
void m1(){ ConcreteBase1::m1())};
void m2(){ ConcreteBase2::m2())};
};


template < typename Base1, typename Base2 >
class Derived : public VirtualBase, private Base1, private Base2
{
void m1(){ Base1::m1())};
void m2(){ Base2::m2())};
} ;

Après, tu y mélanges les implémentations qu'il te faut.

Mais l'idiome classique utilise l'héritage, et dans la mesure où
des ConcreteBase sont conçues pour implémenter des fonctions
dans VirtualBase, je ne vois pas pourquoi elles n'en
hériteraient pas.

OU (je préfère..)

class Derived : public VirtualBase{
public:
void m1(){ c1_.m1())};
void m2(){ c2_.m2())};
private:
ConcreteBase1 c1_;
ConcreteBase2 c2_;
};


En effet, s'il n'y a pas d'héritage de VirtualBase dans les
ConcreteBase, ça vaut encore mieux. (Et là aussi, la solution
template convient.)

Ce qui chagrine mon collègue, c'est d'avoir à définir m1 et m2
deux fois (même si la définition dans la classe dérivée me
semble être une aide pour le lecteur).

Plus clair ou de + en + à coté de la plaque ?


Je ne vois toujours pas de problème avec la solution classique
de mixin, puisque j'ai l'impression que c'est des mixin que tu
veux. Donc, que les classes intermédiaires d'implémentation
héritent de VirtualBase aussi, soit directement, soit par
l'intérmediaire d'une classe de régroupement.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34