OVH Cloud OVH Cloud

private, private et private

23 réponses
Avatar
Fabien LE LEZ
Bonjour,

Dans certaines classes, j'ai trois types de membres privés :

- des membres "réellement" privés, auxquels aucune autre classe n'a
accès ;
- des déclarations de nouvelles fonctions virtuelles (éventuellement
pures) ;
- des implémentations de fonctions virtuelles d'une classe de base.

Exemple :

class B
{
private:
virtual void f()= 0;
};

class D: public B
{
private:
/*virtual*/ void f(); // Fonction qui vient de B.
void g(); // Fonction spécifique à D.
int une_variable_membre;
virtual void h(); // Fonction que les classes héritant de D.
};

J'aimerais savoir comment vous faites pour distinguer visuellement ces
différent types de membres privés.

Mettez-vous trois sections "private:" différentes, toujours dans le
même ordre ?
Y a-t-il d'autres méthodes ?

Question subsidiaire : spécifiez-vous le "virtual" même quand il est
implicite (comme pour B::f()) ?

Merci d'avance...

10 réponses

1 2 3
Avatar
Arnaud Meurgues
Fabien LE LEZ wrote:

Mettez-vous trois sections "private:" différentes, toujours dans le
même ordre ?


Moi, c'est ce que j'aurais tendance à faire.

Question subsidiaire : spécifiez-vous le "virtual" même quand il est
implicite (comme pour B::f()) ?


Je crois qu'il y a différents points de vue. Était-ce Valentin
(quelqu'un sait ce qu'il devient ?) qui disait utiliser cette solution :

class A {
virtual void f();
};

class B : public A {
override void f();
};

avec une définition de override qui pouvait être soit :

#define override
soit
#define override virtual

Le problème est que si l'on met virtuel, on peut rendre virtuel une
fonction qui ne l'était pas et cacher la fonction non virtuelle de
départ. Ne rien mettre, en revanche, demandait de connaître la classe
mère pour savoir si la fonction était ou non virtuelle.

Mettre "override" permettait de signaler qu'on voulait redéfinir une
fonction sans pour autant introduire d'effet de bord.

L'intérêt de changer la définition de rien à virtual permettait aussi de
détecter des erreurs : si le comportement du programme change en
changeant la définition de override, c'est qu'on a fait une erreur sur
la virtualité des fonctions...

J'avais trouvé cela convainquant à l'époque et l'ai adopté pour mon
propre code.
--
Arnaud

Avatar
kanze
Fabien LE LEZ wrote:

Dans certaines classes, j'ai trois types de membres privés :


Moi aussi. Parfois même plus.

- des membres "réellement" privés, auxquels aucune autre classe n'a
accès ;


Disons plutôt, qui ne concerne que de l'implémentation. Des
membres pour lesquels la définition Java de private
conviendrait.

- des déclarations de nouvelles fonctions virtuelles (éventuellement
pures) ;


Prèsque toujours pûres, d'après mon expérience. (Mais si je me
souviens bien, c'était beaucoup moins vrai quand j'ai fait une
GUI.)

Mais pourquoi insistes-tu sur « nouvelles » ? Dans les classes
dérivées, les supplantations sont aussi privées, et j'aurais
tendance à les voir comme faisant partie de la même catégorie
des « privées ». (Mais c'est juste un point de vue.)

- des implémentations de fonctions virtuelles d'une classe de base.


Je ne vois pas ça comme une catégorie distincte de la
précédante. En revanche, j'ai aussi :

-- des declarations faites uniquement pour leurs effets de bord
sur l'interface publique -- la declaration d'un constructeur
de copie ou d'un opérateur d'affectation, surtout, mais
aussi dans certains cas précis, des surcharges des fonctions
publiques, pour empècher certaines conversions implicites
lors de leur appel. (Je crois ne jamais m'être servi de
cette possibilité, mais je sais que la technique existe.)

-- des declarations qui servent à certains amis seulement (par
rapport à celles qui sont réelement privées, et que même les
amis ne doivent pas utiliser).

Exemple :

class B
{
private:
virtual void f()= 0;
};

class D: public B
{
private:
/*virtual*/ void f(); // Fonction qui vient de B.
void g(); // Fonction spécifique à D.
int une_variable_membre;
virtual void h(); // Fonction que les classes héritant de D.
};

J'aimerais savoir comment vous faites pour distinguer
visuellement ces différent types de membres privés.

Mettez-vous trois sections "private:" différentes, toujours
dans le même ordre ?


Grosso modo, c'est ça. Bien que l'ordre n'est pas
rigueureusement fixe ; à part que les membres réelement privés
sont toujours derniers.

Pour les privés utilisés par les amis, j'ajoute un commentaire.
Sinon, c'est assez clair -- si la declaration qui suit private:
est une fonction virtuelle, par exemple, ou si c'est le
constructeur de copie. Ce qui donne :

class C
{
friend class F ;
public:
// ...

private:
virtual void f() = 0 ;
// ...

private:
C( C const& other ) ;
C& operator=( C const& other ) ;

private: // sauf pour F...
// ...

private: // really.
// ...
} ;

Y a-t-il d'autres méthodes ?


Je n'en connais pas, mais je n'en ai pas cherché non plus.
L'idiome des fonctions virtuelles privées et encore plus celui
du constructeur privé sans implémentation sont assez connus pour
qu'ils se passent de commentaires, a mon avis. (Dans le code
unitquement pour moi-même, j'hérite d'une classe
UncopiableObject pour supprimer la copie et l'affectation. Mais
dans le code que les autres doivent aussi comprendre, je préfère
l'idiome classique, immédiatement reconnu, à une astuce à moi,
aussi parlant soit-elle.)

Question subsidiaire : spécifiez-vous le "virtual" même quand
il est implicite (comme pour B::f()) ?


Toujours. Si je sais que la fonction est virtuelle, pourquoi
est-ce que je cacherais le fait de celui qui lit mon code ?

--
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 LE LEZ
On 28 Apr 2006 00:24:29 -0700, "kanze" :

Prèsque toujours pûres, d'après mon expérience.
[...]


Oui.

Mais pourquoi insistes-tu sur « nouvelles » ?


Parce que bien souvent, je n'ai que deux types de fonctions,
complémentaires :
- des fonctions virtuelles pures
- des implémentations, qu'aucune classe dérivée ne change.

- des implémentations de fonctions virtuelles d'une classe de base.


Je ne vois pas ça comme une catégorie distincte de la
précédante.


Ben... C'est exactement l'inverse.

La catégorie précédente ajoute du boulot pour les classes dérivées ;
cette catégorie en enlève.

Typiquement, j'ai souvent un truc de ce genre :

class B
{
virtual int f (int)= 0;
};

class D: public B
{
virtual int g (int)= 0;
virtual int f (int x) { return 2-g(4*x); }
};

class DD: public D
{
virtual int g (int);
// On laisse f(int) telle quelle, elle convient très bien.
};


Avatar
Hamiral
Fabien LE LEZ wrote:

Bonjour,

Dans certaines classes, j'ai trois types de membres privés :

- des membres "réellement" privés, auxquels aucune autre classe n'a
accès ;
- des déclarations de nouvelles fonctions virtuelles (éventuellement
pures) ;
- des implémentations de fonctions virtuelles d'une classe de base.



Moi, depuis que j'ai découvert l'idiome PIMPL (ici même d'ailleurs), je mets
toutes mes données liées uniquement à l'implémentation dans un pimpl, et
tout le reste en protected. Mais bon, je suis encore novice, donc ce n'est
probablement pas la meilleur chose finalement ...

--
Hamiral

Avatar
Fabien LE LEZ
On Fri, 28 Apr 2006 21:21:09 +0200, Hamiral :

Moi, depuis que j'ai découvert l'idiome PIMPL (ici même d'ailleurs), je mets
toutes mes données liées uniquement à l'implémentation dans un pimpl, et
tout le reste en protected.


Groumpf ?

Pimpl est un idiome certes très utile, mais relativement lourd, qu'on
ne doit donc utiliser que quand on en a besoin.
Et quand on a des hiérarchies de classes avec des fonctions virtuelles
en veux-tu en voilà, ça risque d'être assez pénible à gérer.

Quant à mettre "tout le reste en protected", j'ai du mal à voir ce que
tu veux dire.
Là encore, "protected" est utile, mais d'utilisation tout de même
assez limitée. Généralement, une section "protected:" contient assez
peu de fonctions (et aucune variable).

Avatar
Hamiral
Fabien LE LEZ wrote:

On Fri, 28 Apr 2006 21:21:09 +0200, Hamiral :

Moi, depuis que j'ai découvert l'idiome PIMPL (ici même d'ailleurs), je
mets toutes mes données liées uniquement à l'implémentation dans un pimpl,
et tout le reste en protected.


Groumpf ?


Je me suis probablement mal expliqué :
Je mets dans mon pimpl tout ce qui est relatif à l'implémentation de la
classe, et qui donc n'a pas besoin de faire partie de l'interface connue
par l'utilisateur. En général ce sont des données membres et des méthodes
qui servent à d'autres méthodes, et qui
1/ ne devraient pas être héritées
2/ ne devraient être connues que par la classe elle-même

Quant à tout ce que je mets en protected, c'est toutes les données et
méthodes qui ont un sens par rapport à ce que représente la classe : ces
données et méthodes peuvent être susceptibles d'être utilisées par une
classe dérivée.

Pimpl est un idiome certes très utile, mais relativement lourd, qu'on
ne doit donc utiliser que quand on en a besoin.


Relativement lourd ? Pas tant que cela je trouve ...

Et quand on a des hiérarchies de classes avec des fonctions virtuelles
en veux-tu en voilà, ça risque d'être assez pénible à gérer.


Dans ce cas-là effectivement, c'est plus difficile je suis d'accord.

Quant à mettre "tout le reste en protected", j'ai du mal à voir ce que
tu veux dire.


cf plus haut

Là encore, "protected" est utile, mais d'utilisation tout de même
assez limitée. Généralement, une section "protected:" contient assez
peu de fonctions (et aucune variable).


Là je ne suis pas d'accord du tout (cf plus haut).

--
Hamiral


Avatar
Fabien LE LEZ
On Sat, 29 Apr 2006 09:47:35 +0200, Hamiral :

Quant à tout ce que je mets en protected, c'est toutes les données et
méthodes qui ont un sens par rapport à ce que représente la classe


AMHA, c'est une erreur (du moins si je comprends bien ce que tu veux
dire).

Une section "protected", c'est la même chose qu'une section "public" :
c'est une interface. Les destinataires ne sont pas exactement les
mêmes (toutes les classes vs les classes dérivées), mais c'est un
détail -- le principe est le même[*].
Et une interface contient uniquement les fonctions explicitement
faites pour être appelées depuis l'extérieur. Et quasiment jamais de
variables[**].


[*] Note : une section "private" peut être soit une section qui
contient des membres privés, soit une interface -- envers les classes
amies, ou envers les classes dérivées par le truchement de fonctions
virtuelles.

[**] Il arrive que des variables membres soient accessibles à une
fonction extérieure à la classe ; mais dans ce cas, la classe n'a
généralement aucun membre protégé ou privé.
Et, bien souvent (dans mon code au moins), une telle classe n'a aucune
fonction non-const définie explicitement, sauf éventuellement le
constructeur.

Avatar
James Kanze
Arnaud Meurgues wrote:
Fabien LE LEZ wrote:


[...]
Question subsidiaire : spécifiez-vous le "virtual" même quand
il est implicite (comme pour B::f()) ?



Je crois qu'il y a différents points de vue. Était-ce Valentin
(quelqu'un sait ce qu'il devient ?) qui disait utiliser cette
solution :


class A {
virtual void f();
};


class B : public A {
override void f();
};


avec une définition de override qui pouvait être soit :


#define override
soit
#define override virtual


Le problème est que si l'on met virtuel, on peut rendre
virtuel une fonction qui ne l'était pas et cacher la fonction
non virtuelle de départ. Ne rien mettre, en revanche,
demandait de connaître la classe mère pour savoir si la
fonction était ou non virtuelle.


Mettre "override" permettait de signaler qu'on voulait
redéfinir une fonction sans pour autant introduire d'effet de
bord.


L'intérêt de changer la définition de rien à virtual
permettait aussi de détecter des erreurs : si le comportement
du programme change en changeant la définition de override,
c'est qu'on a fait une erreur sur la virtualité des
fonctions...


J'avais trouvé cela convainquant à l'époque et l'ai adopté
pour mon propre code.


I'idée me plaît bien aussi. Seulement... que va penser celui qui
lit mon code et qui rencontre le symbole « override » à cet
endroit ? Ou pire, celui qui inclut mon en-tête, et qui a une
variable local qui s'appelle override ?

La première reflexion m'a fait abandonné ma classe
UncopiableObject, il y a bien quinze ans. Hériter publiquement
(donc, isA) d'une classe qui s'appelle UncopiableObject me
semble bien plus parlant qu'un constructeur de copie et un
opérateur d'affection privé, sans implémentation, mais ce
n'était pas l'idiome consacré, que tout le monde reconnaissait
au premier abord.

Si tu travailles dans une contexte où tu maîtrises toutes les
sources ET tous les programmeurs, que tu introduis la règle dans
tes règles de codages, que tu enforces son utilisation dans les
revues de code, et que tu assures bien que tous les programmeurs
connaissent et utilisent les règles de codage, c'est bien.
Sinon, j'aurais tendance à l'éviter. (Logiquement, ça doit
toujours être le cas dans une société qui produit du code
applicatif. Mais nous savons tous qu'il existe des sociétés où
le procès de développement n'est pas parfait. Et évidemment, si
tu produis les bibliothèques qui doivent servir en dehors de ta
société, tu ne peux absolument pas permettre une telle chose
dans les en-têtes que tu livres.)

--
James Kanze
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
James Kanze
Fabien LE LEZ wrote:
On 28 Apr 2006 00:24:29 -0700, "kanze" :


[...]
- des implémentations de fonctions virtuelles d'une classe de base.
Je ne vois pas ça comme une catégorie distincte de la

précédante.



Ben... C'est exactement l'inverse.


Oui, mais ce sont les mêmes fonctions. Pûre virtuelle, dans la
classe de base ; avec des implémentations dans la classe
dérivée.

Ou est-ce que tu penses au cas où la classe dérivée utilise le
modèle template, pour permettre encore de la customisation ?
Dans ce cas-là, je ferais bien une distinction entre les
fonctions que la classe dérivée implémente en tant
qu'implémentation de l'interface de base, et celle qu'elle
introduit pour permettre sa customisation à elle.

La catégorie précédente ajoute du boulot pour les classes
dérivées ; cette catégorie en enlève.


Typiquement, j'ai souvent un truc de ce genre :

class B
{
virtual int f (int)= 0;
};


class D: public B
{
virtual int g (int)= 0;
virtual int f (int x) { return 2-g(4*x); }
};


class DD: public D
{
virtual int g (int);
// On laisse f(int) telle quelle, elle convient très bien.
};


C'est bien le modèle template, alors. Et on est bien d'accord.
Seulement, j'avoue qu'il n'est pas particulièrement fréquent
chez moi. (Peut-être en fonction des domaines d'application --
quand j'ai fait ma charpente GUI, en Java, j'en avais bien.)

--
James Kanze
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
James Kanze
Hamiral wrote:
Fabien LE LEZ wrote:


Dans certaines classes, j'ai trois types de membres privés :



- des membres "réellement" privés, auxquels aucune autre classe n'a
accès ;
- des déclarations de nouvelles fonctions virtuelles (éventuellement
pures) ;
- des implémentations de fonctions virtuelles d'une classe de base.



Moi, depuis que j'ai découvert l'idiome PIMPL (ici même
d'ailleurs), je mets toutes mes données liées uniquement à
l'implémentation dans un pimpl, et tout le reste en protected.
Mais bon, je suis encore novice, donc ce n'est probablement
pas la meilleur chose finalement ...


Certes, et c'est une bonne reflexe. Même s'il faut pouvoir
reconnaître des cas où il ne convient pas -- une implémentation
de complex avec cet idiome n'est peut-être pas la meilleur idée.
Mais il s'agit ici des fonctions, plus que des données, et il
s'agit surtout des éléments privés dont la présence doit être
connue de l'extérieur, pour une raison ou une autre.

Évidemment, on pourrait décider de declarer tout ce dont parle
Fabien protected. C'est ce que je fais en Java, par exemple, où
private a un sens différent qu'en C++. Et dans le cas des
fonctions virtuelles de l'idiome programmation par contrat, ça
rapproche plus à ce que fait Eiffel, et les origines de la
programmation par contrat. Mais d'après mon expérience, je
trouve que private convient mieux.

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


1 2 3