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
Sylvain
Fabien LE LEZ wrote on 28/04/2006 06:08:

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 ;


celles-ci sont claires/évidentes.

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


là je ne comprends pas le but.

en reprenant ton exemple:

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

- B ne peux pas être instancée,
- toute classe fille devra définir f() mais ne pourra pas appeler la
méthode parent (pour une fille ayant un parent la définissant),
- les utilisateurs de B (et descendants) n'auront jamais accès à f()

dès lors:
qu'apporte la déclaration de f() à B et à ses descendants ? la méthode
étant purement interne, il me parait plus simple d'imaginer que chaque
sous-classe traitera de son comportement interne (masqué) comme bon lui
semble (comme cela ser ale plus pertinent pour chaque classe) -- peut
être certaines filles n'auront pas besoin du tout de f(), peut être que
d'autres voudront f1(), f2(), fn().


le sens d'une virtuelle privée continue à rester confus dans les
commentaires même de ton second exemple:

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


f() "qui vient de B" pourrait laisser comprendre qu'en effet il 'vient'
quelque chose, or ici il vient seulement une absence (une virtualité
pure) qui doit être bouchée (définie) pour pouvoir instancier B

h() "Fonction que les classes héritant de D" (il manque peut être une
suite) - ne pourront pas appeler, - pourront "surcharger" mais sans
avantage/différence par rapport à une déclaration propre de g(), h(), ...


un membre privé est implicitement masqué ("shadow-isé"), le rendre
virtual ne lève pas ce masquage, que reste-t-il alors de virtual?

Sylvain.

Avatar
James Kanze
Sylvain wrote:
Fabien LE LEZ wrote on 28/04/2006 06:08:


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 ;



celles-ci sont claires/évidentes.


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



là je ne comprends pas le but.


C'est sans doute l'idiome de la programmation par contrat.
Certains vont jusqu'à dire que toute fonction virtuelle doit
être privée.

en reprenant ton exemple:


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


« = 0 ».

};


- B ne peux pas être instancée,


C'est probablement le but, ou au moins une partie. B serait une
interface.

- toute classe fille devra définir f() mais ne pourra pas
appeler la méthode parent (pour une fille ayant un parent la
définissant),


Tout à fait. C'est virtuelle pûre -- c'est même probable qu'elle
n'a pas d'implémentation.

- les utilisateurs de B (et descendants) n'auront jamais accès à f()


Pas directement.

Fabien n'a montré que la partie essentielle à sa question ; le
modèle de la programmation par contrat est assez répandue qu'il
l'a sans doute supposé connu. Dans la pratique, évidemment, la
classe ne contient pas que des fonctions virtuelles pûres, mais
aussi des fonctions non-virtuelles publiques qui les appellent,
c-à-d :

class Base
{
public:
void f( char ch )
{
assert( isalpha( ch ) ) ;
doF( ch ) ;
}
Base* clone() const
{
Base* result = doClone() ;
assert( typeid( *result ) == typeid( *this ) ) ;
return result ;
}
// ...

private:
virtual void doF( char ch ) = 0 ;
virtual Base* doClone() = 0 ;
// ...
} ;

dès lors:


[...]
le sens d'une virtuelle privée continue à rester confus dans
les commentaires même de ton second exemple:


Le problème, c'est que Fabien a supposé qu'on reconnaît les
idiomes et les modèles d'où il a extrait ces exemples. Si on ne
connaît pas bien le GoF, ou les autres idiomes courants en C++,
c'est vrai que les extraits qu'il a présenté n'ont pas de sens.
Mais c'est uniquement parce qu'ils sont des extraits --
complétés avec les parties sans importance pour sa question, ce
sont des idiomes tout à fait classiques : celui ci-dessus
touche probablement quelque chose comme 90% ou plus des
hièrarchies polymorphiques dans une application bien écrite.

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



Tandis que celui-ci est bien moins fréquent. Mais je m'en suis
bien servi dans des applications GUI, et je crois que beaucoup
du code de Fabien concerne les GUI.

f() "qui vient de B" pourrait laisser comprendre qu'en effet
il 'vient' quelque chose, or ici il vient seulement une
absence (une virtualité pure) qui doit être bouchée (définie)
pour pouvoir instancier B


h() "Fonction que les classes héritant de D" (il manque peut
être une suite) - ne pourront pas appeler, - pourront
"surcharger" mais sans avantage/différence par rapport à une
déclaration propre de g(), h(), ...


En supposant qu'il s'agit d'une implémentation de B qui lui-même
utilise le modèle de template : conceptuellement, f() est
probablement « invisible » aux classes dérivées -- elle a été
implémentée, et le contrat de B a été rempli. En revanche, les
classes qui dérivent de D sont bien concernées par h() -- c'est
en supplantant h() qu'elles peuvent customiser D.

Pour donner un exemple concret (pas forcément très réaliste,
parce qu'énormement simplifié) :

class GUIComponent
{
public :
void draw() const
{
assert( currentThread == GUIEventThread ) ;
doDraw() ;
}

private:
virtual void doDraw() = 0 ;
} ;

class GUIMessage : public GUIComponent
{
private: // À supplanter dans les classes dérivées
virtual Color* getBackgroundColor() const
{
return &defaultBackgroundColor ;
}
private: // Réelement : en Java, des fonctions final
virtual void doDraw() const
{
// ...
/* ... */ getBackgroundColor() ;
// ...
}
} ;

class GUIAlarm : public GUIMessage
{
private:
virtual Color* getBackgroundColor() const
{
return &red ;
}
} ;

un membre privé est implicitement masqué ("shadow-isé"), le
rendre virtual ne lève pas ce masquage, que reste-t-il alors
de virtual?


Je ne comprends pas exactement ce que tu essaies à dire. Privé
ou non est orthogonal au masquage -- et virtuelle ou non est
orthogonal aux deux.

--
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
Sylvain
James Kanze wrote on 30/04/2006 12:34:

merci James, mais ... ma question ne remettait pas en cause des idiomes

connus (même si je ne les cite pas à chaque point)

C'est sans doute l'idiome de la programmation par contrat.


dont ce fait

Certains vont jusqu'à dire que toute fonction virtuelle doit
être privée.


mais plus ce point et son caractère potentiellement excessif (cf plus bas).

Fabien n'a montré que la partie essentielle à sa question ; le
modèle de la programmation par contrat est assez répandue qu'il
l'a sans doute supposé connu. Dans la pratique, évidemment, la
classe ne contient pas que des fonctions virtuelles pûres, mais
aussi des fonctions non-virtuelles publiques qui les appellent,


nous sommes d'accord sur cet usage; note que je n'avais pas repris, ni
commenté le 3ième type de membres privés listés par Fabien "des
implémentations de fonctions virtuelles d'une classe de base", qui elles
répondent à 100% à cet idiome, comme dans l'exemple Buffer de Stroustrup
(§24.3.2.1) ou dans ta classe Base (pour sa méthode "f").

ta méthode clone soulève 2 problèmes (liés au langage ou à des bugs de
compilos) que tu contournes en introduisant un test sur le type
d'instance créé (pour détecter un oubli de surcharge de doClone dans une
classe fille), tel que tu le codes cela fonctionnera mais forcera un
dynamic_cast pour l'appelant s'il doit vraiment utiliser le clone comme
l'original (y compris pour des fonctions membres non déclarées dans la
classe de base).

or cela illustre une limitation du modifier private, puisque avec les
définitions suivantes (en supposant les csts)

class Base {
public:
Base* clone() {
Base* result = doClone();
assert(typeid(*result) == typeid(*this));
return result;
}
private:
virtual Base* doClone() = null;
};
class Derived : public Base {
private:
virtual Base* doClone() { return new Derived(*this); }
};
class Third : public Derived {
};

(excuses moi d'utiliser "= null" et non "= 0", j'ai cette habitude
depuis de très nombreuses années, je réserve "0" à des numériques, et
"null" à des ptr de fonctions ou des déclarations virtuelles.)

l'appel:

Third t;
t.clone();

sera exécuté; ie Base::clone() est appelée, qui appellera
Derived::clone() qui est pourtant private donc inaccessible à Third -
d'où le assert(typeid) au runtime pour pallier ce point.
c'est le problème 1, peut être lié à certains compilateurs uniquement.

le second problème vient du type retourné par clone; je préférerais les
définitions:

class Base {
public:
virtual Base* clone() = null;
};
class Derived : public Base {
public:
Derived* clone() { return new Derived(*this); }
};
class Third : public Derived {
public:
Third* clone() { return new Third(*this); }
};

ici un dynamic_cast serait économisé, mais (problème 2) certains
compilos ne supportent pas la surcharge du type d'une fonction.

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



En supposant qu'il s'agit d'une implémentation de B qui lui-même
utilise le modèle de template : conceptuellement, f() est
probablement « invisible » aux classes dérivées -- elle a été
implémentée, et le contrat de B a été rempli. En revanche, les
classes qui dérivent de D sont bien concernées par h() -- c'est
en supplantant h() qu'elles peuvent customiser D.


je suis évidemment d'accord avec ça -- je reformule ma question plus
simplement: est-on toujours vraiment certain de savoir ce qui a le droit
d'être appelé par une classe fille et ce qui doit être accessible qu'à
une seule classe ? ou encore n'est-il pas plus sage de se laisser la
possibilité d'invoquer des services parents ? (mes conjectures et
spéculations sur ce qui ne "les" regarde pas a très souvent été
contredit par l'expérience...).

en reprenant ton exemple de composant d'UI, est-ce vraiment
indispensable de chaque classe fille doive définir son
getBackgroundColor() ? j'aurais plutot envie d'avancer le contraire: une
classe parent (type Component) définit une couleur de fond standard
conforme au skin/thème/système et chaque controle comme chaque Container
(ayant une vocation spécifique) /peut/ (et non doit) redéfinir cette
couleur.

j'aurais plutôt envie de dire (de manière approximative, soit):
- une fonction virtuelle doit être privée si elle participe à une tache
simple définie une fois pour toute (ta méthode "f", les méthodes
overflow, undeflow de B.S.)
- une fonction virtuelle doit être protected si elle participe à une
tache complexe impliquant de nombreuses autres méthodes surchargées ou non.

Pour donner un exemple concret (pas forcément très réaliste,
parce qu'énormement simplifié) :

class GUIComponent{
public :
void draw() const {
doDraw() ;
}
private:
virtual void doDraw() = 0 ;
} ;

class GUIMessage : public GUIComponent {
private: // À supplanter dans les classes dérivées
virtual Color* getBackgroundColor() const {
return &defaultBackgroundColor ;
}
private: // Réelement : en Java, des fonctions final
virtual void doDraw() const
{
// ...
/* ... */ getBackgroundColor() ;
// ...
}
} ;


private doDraw ne me parait pas pertinent car il prive de la possibilité
de le surcharger en l'invoquant dans la nouvelle classe - pourquoi
estimer que 'son' implémentation est la meilleure qui ne méritera jamais
le moindre ajout ?, pourquoi obliger l'auteur d'une nouvelle classe a
tout recoder ?

final doDraw ne laisse également perplexe, c'est la méthode draw qui est
vue de l'extérieur, non doDraw, un final draw forcerait l'utilisation de
doDraw et lui seul (et donc sa redéfinition complète); ici il suffira de
surcharger draw et qu'importe le caractère final de doDraw - ce qui
d'ailleurs n'est pas contradictoire avec ta présentation, on peut lire
en effet que draw() ne dépends que d'un doDraw final et que tant que
celui-ci est pertinent, il ne regarde personne de savoir ce qu'il fait
en interne; c'est plus le fait de rendre privé toutes les sous méthodes
utilitaires de doDraw (getBkClr, ...) qui me gène.

un membre privé est implicitement masqué ("shadow-isé"), le
rendre virtual ne lève pas ce masquage, que reste-t-il alors
de virtual?


Je ne comprends pas exactement ce que tu essaies à dire. Privé
ou non est orthogonal au masquage -- et virtuelle ou non est
orthogonal aux deux.


j'aurais du dire "ne renforce pas" la masquage (l'interdiction de
l'appel direct à la fonction d'une classe de base n'est pas renforcé par
l'ajout de virtual puisqu'il est déjà empéché par l'attribut private).

Sylvain.



Avatar
James Kanze
Sylvain wrote:
James Kanze wrote on 30/04/2006 12:34:


ta méthode clone soulève 2 problèmes (liés au langage ou à des
bugs de compilos) que tu contournes en introduisant un test
sur le type d'instance créé (pour détecter un oubli de
surcharge de doClone dans une classe fille), tel que tu le
codes cela fonctionnera mais forcera un dynamic_cast pour
l'appelant s'il doit vraiment utiliser le clone comme
l'original (y compris pour des fonctions membres non déclarées
dans la classe de base).


Pas nécessairement. Dans la pratique, chaque fois que je me suis
amené à fournir une fonction clone, le client se sert uniquement
des Base* (et des fonctions virtuelles).

Si le Derived fournit une interface étendue, il se peut qu'il
soit raisonable de fournir une deuxième fonction de clonage
(cloneDerived ?), disponible uniquement depuis Derived, et qui
renvoie un Derived*. Mais j'avoue que le cas ne s'est jamais
produit dans mon code à moi.

or cela illustre une limitation du modifier private, puisque
avec les définitions suivantes (en supposant les csts)


class Base {
public:
Base* clone() {
Base* result = doClone();
assert(typeid(*result) == typeid(*this));
return result;
}
private:
virtual Base* doClone() = null;
};
class Derived : public Base {
private:
virtual Base* doClone() { return new Derived(*this); }
};
class Third : public Derived {
};


l'appel:


Third t;
t.clone();


sera exécuté; ie Base::clone() est appelée, qui appellera
Derived::clone() qui est pourtant private donc inaccessible à
Third - d'où le assert(typeid) au runtime pour pallier ce
point. c'est le problème 1, peut être lié à certains
compilateurs uniquement.


C'est un exemple de l'erreur qu'on cherche à détecter,
justement. Le contrat de Base dit que toute classe qui en
dérive, même indirectement, doit founir une fonction doClone.
Third ne l'a pas fait -- c'est donc une erreur (une violation du
contrat).

le second problème vient du type retourné par clone; je
préférerais les définitions:


class Base {
public:
virtual Base* clone() = null;
};
class Derived : public Base {
public:
Derived* clone() { return new Derived(*this); }
};
class Third : public Derived {
public:
Third* clone() { return new Third(*this); }
};


ici un dynamic_cast serait économisé, mais (problème 2)
certains compilos ne supportent pas la surcharge du type d'une
fonction.


C'est une nouveauté introduite par la norme. Un compilateur
« moderne » doit le supporter. Mais évidemment, il y a trop
souvent de bonnes raisons de ne pas toujous se servir de la
dernière version du compilateur.

Comme j'ai dit ci-dessus, je ne suis pas réelement en convaincu
de l'utilité. Au moins dans mon style de programmation, je ne
m'en suis jamais senti le besoin. Mais enfin, c'est mon style de
programmation, et c'est peut-être aussi lié aux domaines
d'application où je travaille aussi -- des gens fort compétents
l'ont considéré assez utiles pour l'ajouter à la norme.

Et évidemment, elle rend l'utilisation de la programmation par
contrat impossible dans ce cas-ci. Alors, il faut faire le
choix. En fin de compte, la programmation par contrat n'est
jamais qu'un outil ; ce n'est pas une fin en soi. Si dans un cas
précis, elle coûte plus qu'elle en apporte...

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





En supposant qu'il s'agit d'une implémentation de B qui
lui-même utilise le modèle de template : conceptuellement,
f() est probablement « invisible » aux classes dérivées --
elle a été implémentée, et le contrat de B a été rempli. En
revanche, les classes qui dérivent de D sont bien concernées
par h() -- c'est en supplantant h() qu'elles peuvent
customiser D.



je suis évidemment d'accord avec ça -- je reformule ma
question plus simplement: est-on toujours vraiment certain de
savoir ce qui a le droit d'être appelé par une classe fille et
ce qui doit être accessible qu'à une seule classe ? ou encore
n'est-il pas plus sage de se laisser la possibilité d'invoquer
des services parents ? (mes conjectures et spéculations sur ce
qui ne "les" regarde pas a très souvent été contredit par
l'expérience...).


C'est une des grandes discussions dans la programmation orientée
objet. Mon expérience, c'est qu'au moins en C++, si tu n'as pas
prévue la dimension de customisation dans la classe de base,
elle ne marche pas. Simplement permettre la supplantation n'est
pas suffisante. Mais là aussi, c'est mon expérience, liée
peut-être aux domaines où je suis le plus actif, et ce n'est
certainement pas l'avis de tout le monde.

en reprenant ton exemple de composant d'UI, est-ce vraiment
indispensable de chaque classe fille doive définir son
getBackgroundColor() ?


Non. Si tu régardes l'exemple, tu verrais que j'ai fourni une
implémentation dans la classe qui a déclarée la fonction.

j'aurais plutot envie d'avancer le contraire: une classe
parent (type Component) définit une couleur de fond standard
conforme au skin/thème/système et chaque controle comme chaque
Container (ayant une vocation spécifique) /peut/ (et non doit)
redéfinir cette couleur.


Je me rendais compte même en le postant que l'exemple c'était
extrèmement simplifié -- dans la pratique, évidemment, la
gestion du « look and feel » est beaucoup plus complexe, et et
la classe Message et la classe qui en dérive chercherait en fait
leurs couleurs à partir des objets de gestion de « look and
feel », installer en fonction de l'environement et la
configuration de l'utilisateur. Mon but, c'était de créer un
exemple simple de l'utilisation du modèle template dans
l'implémentation d'une interface, non présenter un modèle de
comment concevoir un charpente de GUI.

j'aurais plutôt envie de dire (de manière approximative, soit):
- une fonction virtuelle doit être privée si elle participe à
une tache simple définie une fois pour toute (ta méthode "f",
les méthodes overflow, undeflow de B.S.)
- une fonction virtuelle doit être protected si elle participe
à une tache complexe impliquant de nombreuses autres méthodes
surchargées ou non.


Il y a là aussi beaucoup de désaccord entre les experts.
Personnellement, je préfère les fonctions privées dans la
plupart de ces cas, mais si le but est d'émuler la programmation
par contrat d'Eiffel (qui reste la référence), protected
conviendrait mieux. En ce qui concerne ta convention, dans la
mesure où 1) elle est documentée, et 2) tu t'en sers
systèmatiquement, je n'y vois pas de problème. Ce n'est pas
exactement la mienne, mais je ne crois pas qu'il y a une vérité
absolue dans cette question.

Pour donner un exemple concret (pas forcément très réaliste,
parce qu'énormement simplifié) :



class GUIComponent{
public :
void draw() const {
doDraw() ;
}
private:
virtual void doDraw() = 0 ;
} ;



class GUIMessage : public GUIComponent {
private: // À supplanter dans les classes dérivées
virtual Color* getBackgroundColor() const {
return &defaultBackgroundColor ;
}
private: // Réelement : en Java, des fonctions final
virtual void doDraw() const
{
// ...
/* ... */ getBackgroundColor() ;
// ...
}
} ;



private doDraw ne me parait pas pertinent car il prive de la
possibilité de le surcharger en l'invoquant dans la nouvelle
classe - pourquoi estimer que 'son' implémentation est la
meilleure qui ne méritera jamais le moindre ajout ?, pourquoi
obliger l'auteur d'une nouvelle classe a tout recoder ?


Dans ce cas précis, c'est un bon point ; on pourrait en
discuter. Comme j'ai dis, j'ai juste cherché un exemple
rapidement que la plupart des gens pourraient comprendre. Je
crois quand même que si la garantie que fait GUIMessage, c'est
d'afficher d'une certaine façon, il faut qu'il contrôle la
fonction doDraw pour s'assurer que cette garantie est remplie.
Mais je reconnais que dans le cas des GUI, c'est difficile de
prévoyer tout -- par exemple, si une classe dérivée veut ajouter
un border, et que GUIMessage n'a pas prévu le cas. (Ce n'est pas
une bonne exemple non-plus, parce qu'un décorateur fait bien
l'affaire. Et on pourrait aussi arguer que c'est déjà une
caractèristique d'un composant d'avoir un arrière-plan et un
border -- en tout cas, Swing le fait, et le composant de base
remplit l'arrière-plan si le composant est opaque, et appelle
une fonction virtuelle distincte pour déssiner le border.)

final doDraw ne laisse également perplexe, c'est la méthode
draw qui est vue de l'extérieur,


Encore une fois, c'est le but de la manip. Afin que draw puisse
enforcer la règle que l'affichage n'a lieu que dans le thread
prévu.

non doDraw, un final draw forcerait l'utilisation de doDraw et
lui seul (et donc sa redéfinition complète);


Je ne suis pas sûr d'avoir compris ici. La fonction doDraw n'est
appelée que depuis draw, dans la base. Personne d'autre ne peut
l'appeler -- jamais. Il vérifie les préconditions, et appelle la
fonction virtuelle, supplantée dans la classe qui a pris la
responsibilité de l'affichage réel.

ici il suffira de surcharger draw et qu'importe le caractère
final de doDraw


Normalement, on ne manipule que les GUIComposant -- au moins
dans les endroits où draw serait appelé.

- ce qui d'ailleurs n'est pas contradictoire avec ta
présentation, on peut lire en effet que draw() ne dépends que
d'un doDraw final et que tant que celui-ci est pertinent, il
ne regarde personne de savoir ce qu'il fait en interne; c'est
plus le fait de rendre privé toutes les sous méthodes
utilitaires de doDraw (getBkClr, ...) qui me gène.


C'est un choix. Ici fortement conditionné par ce que je voulais
montrer. Si je concevais une charpente de GUI réele, je ferais
un analyse beaucoup plus profond de ce qui doit être privée, et
qui non. Intuitivement, je crois que getBackgroundColor serait
privée, mais sans un tel analyse, ce n'est pas une décision que
j'essaierai de défendre.

un membre privé est implicitement masqué ("shadow-isé"),
le rendre virtual ne lève pas ce masquage, que reste-t-il
alors de virtual?




Je ne comprends pas exactement ce que tu essaies à dire.
Privé ou non est orthogonal au masquage -- et virtuelle ou
non est orthogonal aux deux.



j'aurais du dire "ne renforce pas" la masquage (l'interdiction
de l'appel direct à la fonction d'une classe de base n'est pas
renforcé par l'ajout de virtual puisqu'il est déjà empéché par
l'attribut private).


C'est peut-être juste un problème de vocabulaire. J'ai
l'habitude de n'utiliser « masquer » que pour le fait qu'un nom
dans une classe dérivée masque toutes les instances de ce nom
dans des classes de base.

--
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
fabien.chene.nospam
James Kanze writes:

[...] le modèle de la programmation par contrat est assez répandue
qu'il l'a sans doute supposé connu. Dans la pratique, évidemment, la
classe ne contient pas que des fonctions virtuelles pûres, mais
aussi des fonctions non-virtuelles publiques qui les appellent,
[snip]


Qu'appelles-tu "programmation par contrat" ?
L'idiome que je reconnais la, est celui de la template méthode
popularisée par Gamma et al.

Accessoirement, Quel est l'origine du terme "programmation par
contrat" ? Je n'ai pas l'impression que cela désigne les vérifications
de pré/post-conditions, maintenance d'invariants, etc comme on le
trouve dans d'autres langages (voire même dans les papiers pour
C++0X). De là à en déduire qu'il est aussi bien nommé que l'est
*template* méthode ? :)

Le problème, c'est que Fabien a supposé qu'on reconnaît les
idiomes et les modèles d'où il a extrait ces exemples. Si on ne
connaît pas bien le GoF, ou les autres idiomes courants en C++,
c'est vrai que les extraits qu'il a présenté n'ont pas de sens.
[snip]


Je ne trouve pas "programmation par contrat" dans ce que j'assimile à
goF -- via google.

En supposant qu'il s'agit d'une implémentation de B qui
lui-même utilise le modèle de template : conceptuellement, f() est
^^^^^^^^^^^^^^^^^^^^^^^


Est-ce la traduction de template méthode ?

probablement « invisible » aux classes dérivées -- elle a été
implémentée, et le contrat de B a été rempli.



--
Fab

Avatar
Sylvain
Fabien CHÊNE wrote on 03/05/2006 01:41:

Je ne trouve pas "programmation par contrat" dans ce que j'assimile à
goF -- via google.


cela dépend de ce que tu assimiles à "goF"; comme tu ne le précises pas
j'enfonce peut être une porte ouverte (surtout que tu évoques Gamma) ou
fixe le lien: GoF est le raccouri de "Gang of Four" les 4 auteurs de
/Design Patterns/, premier (un des?) ouvrage à définir des règles (ou
idiomes de construction objet.

Sylvain.

Avatar
kanze
Fabien CHÊNE wrote:
James Kanze writes:

[...] le modèle de la programmation par contrat est assez
répandue qu'il l'a sans doute supposé connu. Dans la
pratique, évidemment, la classe ne contient pas que des
fonctions virtuelles pûres, mais aussi des fonctions
non-virtuelles publiques qui les appellent,
[snip]


Qu'appelles-tu "programmation par contrat" ?


Je ne le connais pas d'autre nom. C'est Bertrand Meyer qui en
est l'auteur original je crois.

L'idiome que je reconnais la, est celui de la template méthode
popularisée par Gamma et al.


(Par « template méthode », je suppose que tu veux dire le modèle
template -- le « template pattern » en anglais. Je t'en prie,
pas encore une autre signification pour « méthode ».)

Ce n'est pas vraiment la même chose. Supérficiellement, oui,
mais la finalité en est bien différente. Quand je définis un
contrat dans une classe de base, cette classe est
conceptuellement une interface ; elle n'a pas de comportement
propre à elle. Tandis que dans le modèle de template, le gros du
comportement se trouve dans la classe de base ; les fonctions
virtuelles ne servent qu'à une « customisation » d'un
comportement déjà assez bien défini.

Accessoirement, Quel est l'origine du terme "programmation par
contrat" ?


Autant que je sache, c'est bien Bertrand Meyer qui l'a conçu. Au
départ, il parlait de « design by contract », mais ensuite, il
en a fait une marque déposée. Moi, je l'avais traduit en
« programming by contract » très tôt, parce que je l'implémente
au niveau du programme ; je ne crois pas que j'en suis seul, ni
que j'en étais le premier à faire ce changement. Dans le langage
D, Walter Bright utilise l'expression « contract programming ».

Je n'ai pas l'impression que cela désigne les vérifications de
pré/post-conditions, maintenance d'invariants, etc comme on le
trouve dans d'autres langages (voire même dans les papiers
pour C++0X).


Bien sûr que si. En fait, la technique en question ici, c'est
une technique que j'ai développé exprès pour émuler les
contraints d'Eiffel, en réponse aux questions posées dans
comp.lang.c++.moderated. (À vrai dire, je suis sûr que j'ai vue
une suggestion de l'idée avant. Mais je n'arrive pas à en
trouver la moindre référence. Alors, en attendant, j'en suis
plus ou moins réconnu comme l'auteur de la technique en C++.)

De là à en déduire qu'il est aussi bien nommé que l'est
*template* méthode ? :)


Je tiens à la distinction, malgré les ressemblances
supérficielles. Mais tous les auteurs ne le font pas ; Herb
Sutter confond allegrement les deux concepts, par exemple. Comme
j'ai dit, la différence, ce n'est pas tellement ce que tu fais,
mais pourquoi. Si les fonctions non virtuelles sont là pour
valider le contrat, c'est de la programmation par contrat ; si
elles contiennent la charpente d'une implémentation, voire plus,
c'est le modèle de template.

Le problème, c'est que Fabien a supposé qu'on reconnaît les
idiomes et les modèles d'où il a extrait ces exemples. Si on
ne connaît pas bien le GoF, ou les autres idiomes courants
en C++, c'est vrai que les extraits qu'il a présenté n'ont
pas de sens. [snip]


Je ne trouve pas "programmation par contrat" dans ce que
j'assimile à goF -- via google.


GoF n'ont pas décrit tous les modèles possibles. En fait, le
concepte de la programmation par contrat est bien antérieur au
livre du GoF ; elle faisait partie integrante d'Eiffel déjà dans
les années 1980.

Il y a eu une discussion assez complète sur le sujet sous le
titre « Any Thoughts on Design by Contract in C++ », dans
comp.lang.c++.moderated, à partir du 4 april, 1997 -- c'est là,
je crois, où la plupart des rapprochements à Eiffel ont été
faits. (La plus ancienne référence à la technique que je trouve
date de déc., 1995.)

En supposant qu'il s'agit d'une implémentation de B qui
lui-même utilise le modèle de template : conceptuellement, f() est
^^^^^^^^^^^^^^^^^^^^^^^


Est-ce la traduction de template méthode ?


J'utilise le mot « modèle » assez systèmatiquement comme
traduction de « pattern » (dans « design pattern »). Le « modèle
de template », c'est le « template pattern ».

Comme nomenclature, je ne peux pas dire que ça m'emballe. Déjà,
« modèle », ce n'est pas parfait pour pattern -- mais je ne
trouve rien de mieux. Et puis, quand on entend « template », on
pense tout de suite aux templates de C++, alors qu'ici, ça n'a
rien à voir. (Et là aussi, il n'y a pas de bonne traduction que
je connais. « Pochoir » ou « poncif », ça ne me va pas du tout.)

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

[...] le modèle de la programmation par contrat est assez
répandue qu'il l'a sans doute supposé connu. Dans la
pratique, évidemment, la classe ne contient pas que des
fonctions virtuelles pûres, mais aussi des fonctions
non-virtuelles publiques qui les appellent,
[snip]


Qu'appelles-tu "programmation par contrat" ?


Je ne le connais pas d'autre nom. C'est Bertrand Meyer qui en
est l'auteur original je crois.

L'idiome que je reconnais la, est celui de la template méthode
popularisée par Gamma et al.


(Par « template méthode », je suppose que tu veux dire le modèle
template -- le « template pattern » en anglais.


"template methode" (ou "template methode pattern") est le terme
utilisé par Herb Sutter dans les exceptional C++. Je pensais que
c'était suffisament répendu. Mais je ne suis pas opposé, loin de là, à
l'appeler autrement. (Je ne peut pas m'empêcher de penser qu'un mot
tel que customization aurait pu être utilisé pour désigner ce modèle de
conception.)

Je t'en prie, pas encore une autre signification pour « méthode ».)


Ah bon, il y en a une autre ? ;)

Ce n'est pas vraiment la même chose. Supérficiellement, oui,
mais la finalité en est bien différente. Quand je définis un
contrat dans une classe de base, cette classe est
conceptuellement une interface ; elle n'a pas de comportement
propre à elle. Tandis que dans le modèle de template, le gros du
comportement se trouve dans la classe de base ; les fonctions
virtuelles ne servent qu'à une « customisation » d'un
comportement déjà assez bien défini.


Merci, c'est très clair.


Accessoirement, Quel est l'origine du terme "programmation par
contrat" ?


Autant que je sache, c'est bien Bertrand Meyer qui l'a conçu. Au
départ, il parlait de « design by contract », mais ensuite, il
en a fait une marque déposée. Moi, je l'avais traduit en
« programming by contract » très tôt, parce que je l'implémente
au niveau du programme ; je ne crois pas que j'en suis seul, ni
que j'en étais le premier à faire ce changement. Dans le langage
D, Walter Bright utilise l'expression « contract programming ».

Je n'ai pas l'impression que cela désigne les vérifications de
pré/post-conditions, maintenance d'invariants, etc comme on le
trouve dans d'autres langages (voire même dans les papiers
pour C++0X).


Bien sûr que si. En fait, la technique en question ici, c'est
une technique que j'ai développé exprès pour émuler les
contraints d'Eiffel, en réponse aux questions posées dans
comp.lang.c++.moderated. (À vrai dire, je suis sûr que j'ai vue
une suggestion de l'idée avant. Mais je n'arrive pas à en
trouver la moindre référence. Alors, en attendant, j'en suis
plus ou moins réconnu comme l'auteur de la technique en C++.)


Au temps pour moi. Peut-on parvenir à séparer logiquement les
préconditions/postconditions, des invariants d'une classe ?


De là à en déduire qu'il est aussi bien nommé que l'est
*template* méthode ? :)


Je tiens à la distinction, malgré les ressemblances
supérficielles. Mais tous les auteurs ne le font pas ; Herb
Sutter confond allegrement les deux concepts, par exemple. Comme
j'ai dit, la différence, ce n'est pas tellement ce que tu fais,
mais pourquoi. Si les fonctions non virtuelles sont là pour
valider le contrat, c'est de la programmation par contrat ; si
elles contiennent la charpente d'une implémentation, voire plus,
c'est le modèle de template.



Et je te rejoins surement quand, à un autre endroit dans ce fil, tu
dis ne pas trop utiliser le modèle de template. Je pense que tu leur
préfèrent alors les Mixins.

Je me rend compte qu'une partie non négligeable du code que j'écris ne
correspond ni à la programmation par contrat, ni vraiment au modèle de
template puisque l'implémentation des fonctions publiques non
virtuelles consistent à appeler leur homologues do*. Parfois avec
préconditions, mais rarement. J'ai toutefois bien l'assert sur la
fonction doClone() par exemple, parce que cela me parait
indispensable.

En fait, je pense mixin d'abord, et j'opte pour une autre solution
lorsque la possibilité de customization est réduite. En cas de
nouvelles possibilitées de customization, mon code évolue alors
facilement vers une méthode de template. Cela me parait moins risqué
que de faire une mixin un peu triviale, qui probablement posera
d'avantage de problèmes de maintenance.

Question subsidiaire : est-il imaginable de faire de la programmation
par contrat, tout en utilisant la méthode de template ou les Mixins ?
Ces deux choses m'ont l'air a priori orthogonales.


En supposant qu'il s'agit d'une implémentation de B qui
lui-même utilise le modèle de template : conceptuellement, f() est
^^^^^^^^^^^^^^^^^^^^^^^


Est-ce la traduction de template méthode ?


J'utilise le mot « modèle » assez systèmatiquement comme
traduction de « pattern » (dans « design pattern »). Le « modèle
de template », c'est le « template pattern ».


Et moi, je traduis souvent template par modèle. Ce qui fait que modèle
de template me parait pour le moins abstrait.

--
Fab



Avatar
Fabien LE LEZ
On Wed, 03 May 2006 20:52:17 +0200, Fabien CHÊNE :

Je t'en prie, pas encore une autre signification pour « méthode ».)


Ah bon, il y en a une autre ? ;)


Je n'en connais que trois autres, mais je ne suis pas très au fait du
vocabulaire.


Avatar
fabien.chene.nospam
(Fabien CHÊNE) writes:


Merci de substituer (dans le message auquel je répond) modèle de
template à méthode de template. J'ai voulu essayer de changer mes
habitudes mais n'y suis pas parvenu :-)

--
Fab
1 2 3