OVH Cloud OVH Cloud

Sous-classe ou pas sous-classe ?

6 réponses
Avatar
none
Bonsoir à tous !

J'ai un problème simple à vous soumettre.
J'ai écrit une classe qui permet de gérer le parsing d'un fichier dans
un format spécifique (chargement, sauvegarde, lecture/écriture de
paramètres, etc).
Cette classe publique A encapsule une liste d'instances de classe B qui
représentent autant de sections de mon fichier.
Pour l'instant, ça ressemble à peu prés à ça :

class B
{
...
};

class A
{
private:
List<B*> m_sections;
};


Je souhaiterais que cette classe B ne soit pas publique.
Dois-je en faire une sous-classe de A ? Est-ce que ça ne risque pas de
gêner la lisibilité de mon source ?
Autre chose, comme je suis loin de maîtriser ne serait-ce qu'un centième
de ce merveilleuse langage qu'est le C++, je me pose également la
question de l'utilité de déclarer m_sections comme une list de B plutôt
que d'une liste de B*. Pour l'instant, je dois vider manuellement
m_sections à chaque fois que j'ai besoin de le réinitialiser.
Mais j'ai peur qu'en écrivant "List<B> m_sections;" je risque de
multiplier les instanciations et les recopies d'objets lorsque je
voudrais parcourir ma liste et en utiliser les éléments.
Merci d'avance pour vos lumières.

6 réponses

Avatar
Marc Boyer
Le 07-02-2006, none <""> a écrit :
J'ai un problème simple à vous soumettre.
J'ai écrit une classe qui permet de gérer le parsing d'un fichier dans
un format spécifique (chargement, sauvegarde, lecture/écriture de
paramètres, etc).
Cette classe publique A encapsule une liste d'instances de classe B qui
représentent autant de sections de mon fichier.
Pour l'instant, ça ressemble à peu prés à ça :

class B
{
...
};

class A
{
private:
List<B*> m_sections;
};


Je souhaiterais que cette classe B ne soit pas publique.


class A {
class B {
friend class A;
}
std::list<B> m_sections;
public:
A(){};
}

Dois-je en faire une sous-classe de A ? Est-ce que ça ne risque pas de
gêner la lisibilité de mon source ?


Lisibilité de ton source ?
Tu n'es pas obligé de mettre le code des méthodes de B dans sa
définition dans A.

Autre chose, comme je suis loin de maîtriser ne serait-ce qu'un centième
de ce merveilleuse langage qu'est le C++, je me pose également la
question de l'utilité de déclarer m_sections comme une list de B plutôt
que d'une liste de B*. Pour l'instant, je dois vider manuellement
m_sections à chaque fois que j'ai besoin de le réinitialiser.
Mais j'ai peur qu'en écrivant "List<B> m_sections;" je risque de
multiplier les instanciations et les recopies d'objets lorsque je
voudrais parcourir ma liste et en utiliser les éléments.


Puisque tu te dis débutant:
1) écrit un code correct, en laissant le compilo faire
le max de boulot (et entre autre vider ton m_section)
2) quand ça tournera, interroge toi sur les perfs.

En l'occurence, comme je ne connais pas List, il
m'est difficile de dire quoi que ce soit sur ce dont
tu parles.
Mais avec std::list et des accès par itérateurs,
non, à moins que tu le fasses exprès, il n'y a pas
de raison d'avoir moultes recopies inutiles.

Marc Boyer
--
Entre le fort et le faible, c'est la liberte qui opprime et le droit
qui libere. Henri Lacordaire, Dominicain

Avatar
Jean-Marc Bourguet
none <""(none)"@(none)"> writes:

Bonsoir à tous !

J'ai un problème simple à vous soumettre.
J'ai écrit une classe qui permet de gérer le parsing d'un fichier dans un
format spécifique (chargement, sauvegarde, lecture/écriture de paramètres,
etc).
Cette classe publique A encapsule une liste d'instances de classe B qui
représentent autant de sections de mon fichier.
Pour l'instant, ça ressemble à peu prés à ça :

class B
{
...
};


Tu peux avoir simplement une declaration pour B, la definition n'a pas
l'air necessaire. Donc

class B;

suffit et tu peux la mettre dans A.

class A
{
private:
List<B*> m_sections;
};


Je souhaiterais que cette classe B ne soit pas publique.
Dois-je en faire une sous-classe de A ? Est-ce que ça ne risque pas de
gêner la lisibilité de mon source ?
Autre chose, comme je suis loin de maîtriser ne serait-ce qu'un centième de
ce merveilleuse langage qu'est le C++, je me pose également la question de
l'utilité de déclarer m_sections comme une list de B plutôt que d'une liste
de B*. Pour l'instant, je dois vider manuellement m_sections à chaque fois
que j'ai besoin de le réinitialiser.


A priori des sections d'un fichier ca n'a pas une semantique de
valeur, donc tu n'as de constructeur de copie ni d'operateur
d'affectation accessibles et la question ne se pose pas, il faut
manipuler des pointeurs plus ou moins encapsules.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
kanze
Marc Boyer wrote:
J'ai un problème simple à vous soumettre. J'ai écrit une
classe qui permet de gérer le parsing d'un fichier dans un
format spécifique (chargement, sauvegarde, lecture/écriture
de paramètres, etc).

Cette classe publique A encapsule une liste d'instances de
classe B qui représentent autant de sections de mon fichier.
Pour l'instant, ça ressemble à peu prés à ça :

class B
{
...
};

class A
{
private:
List<B*> m_sections;
};

Je souhaiterais que cette classe B ne soit pas publique.


class A {
class B {
friend class A;
}
std::list<B> m_sections;
public:
A(){};
}

Dois-je en faire une sous-classe de A ? Est-ce que ça ne
risque pas de gêner la lisibilité de mon source ?


Lisibilité de ton source ?
Tu n'es pas obligé de mettre le code des méthodes de B dans
sa définition dans A.


Il n'est même pas obligé à mettre la définition de la classe B
dans la classe A. Au moins s'il n'utilise que std::list<B*> --
la norme dit que std::list<B> a un comportement indéfini si B
est un type incomplet.

Autre chose, comme je suis loin de maîtriser ne serait-ce
qu'un centième de ce merveilleuse langage qu'est le C++, je
me pose également la question de l'utilité de déclarer
m_sections comme une list de B plutôt que d'une liste de B*.
Pour l'instant, je dois vider manuellement m_sections à
chaque fois que j'ai besoin de le réinitialiser. Mais j'ai
peur qu'en écrivant "List<B> m_sections;" je risque de
multiplier les instanciations et les recopies d'objets
lorsque je voudrais parcourir ma liste et en utiliser les
éléments.


Puisque tu te dis débutant:
1) écrit un code correct, en laissant le compilo faire
le max de boulot (et entre autre vider ton m_section)


À mon avis, ça dépend beaucoup de la nature de B. Si c'est une
simple valeur, avec copie et affectation, d'accord. S'il a un
comportement en plus, c'est moins évident -- l'utilisation des
pointeurs assure l'identité et permet le polymorphisme, deux
choses qui peuvent être importantes si la classe a un
comportement à elle.

Sans savoir plus sur ce qu'il fait exactement, c'est difficile à
dire plus. Dans le cas des fichiers de configuration de type
Windows, par exemple, je me sers directement d'un :

std::map< std::string, std::map< std::string, std::string > >

(écrit au moyen des typedef) dans les cas simples, et je me
passe de la classe imbriquée. On pourrait arguer que la copie
des map entiers est chers, mais en fait, tels qu'on l'utilise,
il n'y a jamais réelement de copie (même si la norme exige un
constructeur de copie et d'affectation) -- on profite du fait
que l'operator[] crée un nouvel élément automatiquement.

2) quand ça tournera, interroge toi sur les perfs.

En l'occurence, comme je ne connais pas List, il
m'est difficile de dire quoi que ce soit sur ce dont
tu parles.
Mais avec std::list et des accès par itérateurs,
non, à moins que tu le fasses exprès, il n'y a pas
de raison d'avoir moultes recopies inutiles.


C'est en général le cas pour les collections basées sur des
noeuds. N'empèche qu'il y a une copie lors de l'insertion, et
que si la copie de B est extrèmement chère, ça pourrait jouer
(selon ce qu'il fait, évidemment).

Beaucoup plus problèmatique, c'est le cas où B ne supporte pas
la copie, parce qu'il a une identité. Logiquement, si B
représente une section d'un fichier, où je pourrais effectuer
des modifications qui serait réécrit dans le fichier, j'aurais
tendance à penser qu'il ne supporte pas la copie, parce
qu'effectuer des modifications sur plusieurs copies de la même
section ne donnera pas le résultat qu'on veut. (À moins que B ne
soit qu'une façade pour une implémentation unique de chaque
section. Mais ça m'étonnerait s'il est aussi débutant qu'il se
dit.)

--
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
fabien.chene.nospam
"kanze" writes:


Il n'est même pas obligé à mettre la définition de la classe B
dans la classe A. Au moins s'il n'utilise que std::list<B*> --
la norme dit que std::list<B> a un comportement indéfini si B
est un type incomplet.


Ah ?
gotw Item 27, Herb nous dit que l'instanciation de std::list<B>
ne requiert théoriquement pas à B d'être un type complet -- même si
certains compilateurs en éprouvent le besoin.

A l'époque ou j'avais lu ça, j'avais été surpris. Mais l'ODR ( 3.2 ),
m'avait convaincu. Mais bon, vu les ramifications de ce chapitre, il
est largement probable que j'ai manqué le petit alinéa décisif.
Qu'en est il ?


-- Fab --

Avatar
kanze
Fabien CHÊNE wrote:
"kanze" writes:

Il n'est même pas obligé à mettre la définition de la classe
B dans la classe A. Au moins s'il n'utilise que
std::list<B*> -- la norme dit que std::list<B> a un
comportement indéfini si B est un type incomplet.


Ah ?

gotw Item 27, Herb nous dit que l'instanciation de
std::list<B> ne requiert théoriquement pas à B d'être un type
complet -- même si certains compilateurs en éprouvent le
besoin.


Tiens, je me serais attendu à la contraire. La norme dit
clairement (§17.4.3.6/2) : « In particular, the effects are
undefined in the following cases: [...] -- if an incomplete type
is used as a template argument when instantiating a template
component. » En revanche, tant qu'on n'instantie que la
définition de la classe (et non les définitions de ses membres),
j'imagine que ça passe sur la quasi-totalité des
implémentations.

Note que l'article de Herb que tu cites date de 1997. Avant la
norme, en fait. Je crois que le texte final était déjà étabil à
cette date, mais ce n'est pas dit que tout le monde l'avait déjà
lu en détail et s'en est rendu compte de toutes ses
implications. Si on considère l'implémentation SGI
(originalement HP) de la STL, et les spécifications exactes de
quand un compilateur doit instancier quoi dans les templates, il
n'y a pas de problème avec le type incomplet -- de même
qu'aujourd'hui, il marche en fait avec pratiquement toutes les
implémentations. En revanche, il y avait pas mal de compilateurs
alors qui instantiait toute la classe, y compris toutes les
fonctions membres, comme un ensemble. Ce qui poserait pas mal de
problèmes dans la pratique.

A l'époque ou j'avais lu ça, j'avais été surpris. Mais l'ODR (
3.2 ), m'avait convaincu. Mais bon, vu les ramifications de ce
chapitre, il est largement probable que j'ai manqué le petit
alinéa décisif. Qu'en est il ?


La norme n'a pas cherché à délimiter sur chaque cas séparément.
Elle a pris la solution de faire une interdiction globale. C'est
donc un comportement indéfini. Dans la pratique, c'est un de ces
cas où si ça compile, ça marcherait. Et aussi, ça compilera en
fait, sauf dans les compilateurs anciens qui instantient toutes
les fonctions membre chaque fois qu'ils instantient la classe.
(Ce qui poserait d'autres problèmes aussi.)

--
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
fabien.chene.nospam
"kanze" writes:

Fabien CHÊNE wrote:
"kanze" writes:

Il n'est même pas obligé à mettre la définition de la classe
B dans la classe A. Au moins s'il n'utilise que
std::list<B*> -- la norme dit que std::list<B> a un
comportement indéfini si B est un type incomplet.


Ah ?

gotw Item 27, Herb nous dit que l'instanciation de
std::list<B> ne requiert théoriquement pas à B d'être un type
complet -- même si certains compilateurs en éprouvent le
besoin.


Tiens, je me serais attendu au contraire. La norme dit clairement
(§17.4.3.6/2) : « In particular, the effects are undefined in the
following cases: [...] -- if an incomplete type is used as a
template argument when instantiating a template component. » En
revanche, tant qu'on n'instantie que la définition de la classe (et
non les définitions de ses membres), j'imagine que ça passe sur la
quasi-totalité des implémentations.


Merci pour la référence. Vu que celle-ci apparaît dans la section
bibliothèque standard, j'imagine que l'alinéa que tu cites ne
s'applique alors qu'aux composants de la SL. Ainsi, un ::My::List<T>
serait légal tandis qu'un std::list<T> non ? (avec T incomplet).

Note que l'article de Herb que tu cites date de 1997. Avant la
norme, en fait. Je crois que le texte final était déjà étabil à
cette date, mais ce n'est pas dit que tout le monde l'avait déjà
lu en détail et s'en est rendu compte de toutes ses
implications. Si on considère l'implémentation SGI
(originalement HP) de la STL, et les spécifications exactes de
quand un compilateur doit instancier quoi dans les templates, il
n'y a pas de problème avec le type incomplet -- de même
qu'aujourd'hui, il marche en fait avec pratiquement toutes les
implémentations. En revanche, il y avait pas mal de compilateurs
alors qui instantiait toute la classe, y compris toutes les
fonctions membres, comme un ensemble. Ce qui poserait pas mal de
problèmes dans la pratique.

A l'époque ou j'avais lu ça, j'avais été surpris. Mais l'ODR (
3.2 ), m'avait convaincu. Mais bon, vu les ramifications de ce
chapitre, il est largement probable que j'ai manqué le petit
alinéa décisif. Qu'en est il ?


La norme n'a pas cherché à délimiter sur chaque cas séparément.
Elle a pris la solution de faire une interdiction globale. C'est
donc un comportement indéfini. Dans la pratique, c'est un de ces
cas où si ça compile, ça marcherait.


C'est bien comme cela que je l'appréhende. Je ne sais pas si c'est
bien raisonnable. Mais vu que le code que j'écris n'est pas destiné
à fonctionner sur des compilateurs antédiluviens ... Je crois que je
vais opter pour la politique du ça-compile-ça-marche :/

Et aussi, ça compilera en fait, sauf dans les compilateurs anciens
qui instantient toutes les fonctions membre chaque fois qu'ils
instantient la classe. (Ce qui poserait d'autres problèmes aussi.)



-- Fab --