OVH Cloud OVH Cloud

Heritage d'inner class

9 réponses
Avatar
Aurelien Regat-Barrel
Bonjour à tous,
Je viens de réaliser qu'il était possible de faire de l'héritage entre
inner classes. Il y a même pleins de possibilités:

class A
{
public:
class PublicInnerA {};
protected:
class ProtectedInnerA {};
};

class B : public A::PublicInnerA {};

class C : public A
{
class InnerC;
};

class C::InnerC : public A::ProtectedInnerA
{
};

je trouve ce procédé génial, je vais m'efforcer d'entre trouver une
utilité ;-)
Plus sérieusement, je ne l'ai jamais croisé, et m'en étonne. N'y a-t-il
pas de technique particulière qui l'utilise ? Un cas typique où ça se
révèle une solution élégante ?

Merci.

--
Aurélien Regat-Barrel

9 réponses

Avatar
Fabien LE LEZ
On Thu, 02 Feb 2006 14:03:59 +0100, Aurelien Regat-Barrel
:

je trouve ce procédé génial, je vais m'efforcer d'entre trouver une
utilité ;-)


Barf... Une classe est une classe, qu'elle soit membre d'une autre
classe ou pas.
En pratique, s'il s'agit d'isoler des classes, les mettre dans un
namespace dédié est une solution au moins aussi fréquente (du moins
dans mon code) que les mettre comme classes membres.

Avatar
Jean-Sebastien Mouret
Aurelien Regat-Barrel writes:

Bonjour à tous,
Je viens de réaliser qu'il était possible de faire de l'héritage entre
inner classes. Il y a même pleins de possibilités:

[pleins de possibilités]


je trouve ce procédé génial, je vais m'efforcer d'entre trouver une
utilité ;-)
Plus sérieusement, je ne l'ai jamais croisé, et m'en étonne. N'y a-t-il
pas de technique particulière qui l'utilise ? Un cas typique où ça se
révèle une solution élégante ?



Les traits sont un exemple de ce genre de construction,
sauf que l'on utilise plutôt des typedefs, ce qui revient au même.

Un exemple sympa serait une classe container avec la définition
de son itérateur à l'intérieur même de celle du container.

typedef<typename T>
class container
{
class iterator
{
//...



--
js

Avatar
kanze
Aurelien Regat-Barrel wrote:

Je viens de réaliser qu'il était possible de faire de
l'héritage entre inner classes.


Une première remarque : j'éviterais le vocable « inner class ».
En C++, on parle d'habitude des « nested class », ou en
français, des classes embriquées. Et le C++ n'a rien qui
ressemble aux « inner class » de Java. (En Java, un « inner
class », c'est un « nested class » qui contient une référence
implicite à un objet de la classe qui le contient -- on ne peut
en créer une instance que dans une fonction membre non-statique
de la classe, où on a un pointeur this pour initialiser cette
référence. Rien à voir, donc, avec les classes imbriquées du
C++, ni même avec les autres classes imbriquées de Java.)

Il y a même pleins de
possibilités:

class A
{
public:
class PublicInnerA {};
protected:
class ProtectedInnerA {};
};

class B : public A::PublicInnerA {};

class C : public A
{
class InnerC;
};

class C::InnerC : public A::ProtectedInnerA
{
};

je trouve ce procédé génial, je vais m'efforcer d'entre
trouver une utilité ;-)


Je dirais que c'est assez fréquent, même. Je ne crois pas avoir
fait une application qui ne s'en servait pas.

Je l'utilise beaucoup aussi dans des classes templatées un peu
complexe, de peu que la classe en question utilise des noeuds ou
d'autres classes « implémentation ». Il y a une classe de base
non templatée qui factorise la partie vraiment générique (c-à-d
qui ne dépend pas des paramètres d'instantiation du template).
Donc, par exemple, j'ai une classe :

class HashTableImpl
{
// ...
protected:
class Node
{
// ...
} ;
} ;

template< typename T >
class AssocArrayOf : public HashTableImpl
{
// ...
struct TypedNode : Node
{
T value ;
// ...
} ;
} ;

(Dans ce cas-ci, HashTableImpl contient toute la gestion du
table haché, mais compte sur la classe dérivée pour les
opérations qui dépend du type, comme calculer le hachage,
comparer deux valeurs pour égalité, ou créer et detruire les
noeuds.)

Plus sérieusement, je ne l'ai jamais croisé, et m'en étonne.


C'est cependant une technique fréquente -- dans l'implémentation
des templates, pour éviter le code bloat, comme ci-dessus, mais
aussi pour des cas comme les Visitor, ou les Actions dans une
transaction.

N'y a-t-il pas de technique particulière qui l'utilise ? Un
cas typique où ça se révèle une solution élégante ?


Voir ci-dessus. Aussi le modèle visiteur :

class Visitable
{
public :
class Visitor
{
public:
virtual ~Visitor() {}
virtual void doSomething( Visitable& obj ) = 0 ;
} ;
// ...
} ;

Aussi pour les actions associées à une transaction. Ou pour les
Undo. (Selon la façon qu'on gère les transactions.)

--
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

Avatar
Aurelien Regat-Barrel

je trouve ce procédé génial, je vais m'efforcer d'entre
trouver une utilité ;-)



Je dirais que c'est assez fréquent, même. Je ne crois pas avoir
fait une application qui ne s'en servait pas.

Je l'utilise beaucoup aussi dans des classes templatées un peu
complexe, de peu que la classe en question utilise des noeuds ou
d'autres classes « implémentation ». Il y a une classe de base
non templatée qui factorise la partie vraiment générique (c-à-d
qui ne dépend pas des paramètres d'instantiation du template).
Donc, par exemple, j'ai une classe :

class HashTableImpl
{
// ...
protected:
class Node
{
// ...
} ;
} ;

template< typename T >
class AssocArrayOf : public HashTableImpl
{
// ...
struct TypedNode : Node
{
T value ;
// ...
} ;
} ;

(Dans ce cas-ci, HashTableImpl contient toute la gestion du
table haché, mais compte sur la classe dérivée pour les
opérations qui dépend du type, comme calculer le hachage,
comparer deux valeurs pour égalité, ou créer et detruire les
noeuds.)


Plus sérieusement, je ne l'ai jamais croisé, et m'en étonne.



C'est cependant une technique fréquente -- dans l'implémentation
des templates, pour éviter le code bloat, comme ci-dessus, mais
aussi pour des cas comme les Visitor, ou les Actions dans une
transaction.


Ah dans les templates peut être. En fait ce procédé me fait penser à la
technique du pimpl, mais où le pimpl serait implémenté par les classes
filles. C'est de la paramétrisation de classe mère en quelque sorte,
comme les templates, sauf qu'il y a une classe mère commune, et que
c'est pas "ouvert" (choix du paramètre par l'utilisateur).
Bref, une manière de factoriser du code, sans template.

Je l'ai dit, je m'efforce d'en trouver une utilité. Mais typiquement,
j'ai déjà écrit ce genre de code (en simplifié toujours):

class Base
{
public:
int DoSomething()
{
return this->GetSomething1() + this->GetSomething2();
}

protected:
virtual int GetSomething1() = 0;
virtual int GetSomething2() = 0;
};

Isoler l'interface à implémenter dans une nested class ne serait-il pas
plus élégant ?

class Base
{
protected:
class BaseInterface
{
public:
virtual int GetSomething1() = 0;
virtual int GetSomething2() = 0;
};

public:
int DoSomething()
{
return i_->GetSomething1() + i_->GetSomething2();
}

protected:
Base( BaseInterface *I ) : i_( I ) {}

private:
BaseInterface *i_;
};

--
Aurélien Regat-Barrel


Avatar
Aurelien Regat-Barrel
J'avais fait un longue réponse mais je comprends pas j'ai du faire une
fausse manip elle semble s'être perdu :-(

je trouve ce procédé génial, je vais m'efforcer d'entre trouver une
utilité ;-)



Barf... Une classe est une classe, qu'elle soit membre d'une autre
classe ou pas.


La grande différence je trouve c'est que si la nested class est
protected on peut restreindre son héritage, qui doit passer par un
héritage de la classe "racine".
Dans le cas de 2 classes fortement couplées, ça me parrait intéressant.
En particulier pour des hiérarchies d'héritage double. Par exemple, une
classe Object et ObjectID. J'aurai eu tendance à faire:

class ObjectID
{
public:
vitual ~ObjectID() = 0;
};

class Object
{
public:
virtual ObjectID* GetID() = 0;
};

ObjectID n'a rien à faire seul, il n'a de sens qu'avec Object. Les
regrouper ensembles me semble meilleur:

class Object
{
protected:
class ObjectID { public: vitual ~ObjectID() = 0; };

public:
virtual ObjectID* GetID() = 0;
};

On peut parfaitement utiliser ObjectID, mais on ne peut pas en hériter
directement : il faut aussi spécialiser Object. C'est un héritage
conditionnel en quelque sorte.
Ca me parrait une possibilité intéressante, mais je n'en voit pas trop
le champ d'application.

En pratique, s'il s'agit d'isoler des classes, les mettre dans un
namespace dédié est une solution au moins aussi fréquente (du moins
dans mon code) que les mettre comme classes membres.


--
Aurélien Regat-Barrel


Avatar
kanze
Aurelien Regat-Barrel wrote:

[...]

Je l'ai dit, je m'efforce d'en trouver une utilité. Mais
typiquement, j'ai déjà écrit ce genre de code (en simplifié
toujours):

class Base
{
public:
int DoSomething()
{
return this->GetSomething1() + this->GetSomething2();
}

protected:
virtual int GetSomething1() = 0;
virtual int GetSomething2() = 0;
};

Isoler l'interface à implémenter dans une nested class ne serait-il p as
plus élégant ?

class Base
{
protected:
class BaseInterface
{
public:
virtual int GetSomething1() = 0;
virtual int GetSomething2() = 0;
};

public:
int DoSomething()
{
return i_->GetSomething1() + i_->GetSomething2();
}

protected:
Base( BaseInterface *I ) : i_( I ) {}

private:
BaseInterface *i_;
};


Modèle de template, contre modèle de stratégie. En général, je
préfère le modèle de stratégie, mais ce n'est pas une règle
absolue. Le modèle de template sert surtout quand tu as de bons
défauts, et donc une implémentation dans la classe de base.
Dans ces cas-là, souvent, on peut utiliser la classe de base
sans en dériver ; on n'en dérive que pour modifier le
comportement de base. (Ça arrive souvent dans les hiérarchies de
GUI.)

Si tu veux utiliser le polymorphisme dans le constructeur, il
n'y a que le modèle de stratégie qui marche. Et encore, il faut
faire gaffe à l'ordre d'initialisation, au moins que tu exiges
que la classe déléguée soit allouée dynamiquement, et libérée
dans le destructeur de la Base. (Dans ce cas-là, je suggèrerais
l'utilisation de std::auto_ptr, déjà comme paramètre au
constructeur de la classe de base.)

--
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

Avatar
Aurelien Regat-Barrel

Modèle de template, contre modèle de stratégie. En général, je
préfère le modèle de stratégie, mais ce n'est pas une règle
absolue. Le modèle de template sert surtout quand tu as de bons
défauts, et donc une implémentation dans la classe de base.
Dans ces cas-là, souvent, on peut utiliser la classe de base
sans en dériver ; on n'en dérive que pour modifier le
comportement de base. (Ça arrive souvent dans les hiérarchies de
GUI.)

Si tu veux utiliser le polymorphisme dans le constructeur, il
n'y a que le modèle de stratégie qui marche. Et encore, il faut
faire gaffe à l'ordre d'initialisation, au moins que tu exiges
que la classe déléguée soit allouée dynamiquement, et libérée
dans le destructeur de la Base. (Dans ce cas-là, je suggèrerais
l'utilisation de std::auto_ptr, déjà comme paramètre au
constructeur de la classe de base.)


D'accord. Merci pour ces précisions.

--
Aurélien Regat-Barrel

Avatar
Luc Hermitte
Bonsoir,

Aurelien Regat-Barrel wrote in news:43e20366$0
$282$:

Je viens de réaliser qu'il était possible de faire de l'héritage entre
inner classes. Il y a même pleins de possibilités:
[...]
je trouve ce procédé génial, je vais m'efforcer d'entre trouver une
utilité ;-)
Plus sérieusement, je ne l'ai jamais croisé, et m'en étonne. N'y a-t-il
pas de technique particulière qui l'utilise ? Un cas typique où ça se
révèle une solution élégante ?



Comme ça, je pense aux mixin-layers (assimilables à du CRTP en couches) au
sujet desquels Smaragdakis a écrit une thèse, en 99, disponible sur le net.

--
Luc Hermitte <hermitte at free.fr>
FAQ de <news:fr.comp.lang.c++> :
<http://www.cmla.ens-cachan.fr/Utilisateurs/dosreis/C++/FAQ/>
Dejanews : <http://groups.google.com/advanced_group_search>

Avatar
Aurelien Regat-Barrel
Comme ça, je pense aux mixin-layers (assimilables à du CRTP en couches) au
sujet desquels Smaragdakis a écrit une thèse, en 99, disponible sur le net.


Je crois que j'avais imprimé un long papier sur le sujet, mais j'ai pas
eu le cran pour m'y atteler... Je vais m'y repencher à l'occasion.
Merci.

--
Aurélien Regat-Barrel