OVH Cloud OVH Cloud

[debutant] operator

30 réponses
Avatar
Bruno CAUSSE
Bonjour,

j'aimerai comprendre (dans le detail) cette declaration :

friend std::ostream& operator<<(std::ostream& stream, const maClass& a);

A) Est declaré dans maClass mais n'est pas une fonction membre de maClass.

B) Est declaré dans maClass avec le modificateur friend pour acceder a
chaques membres de maClass.

C) en ecrivant la declaration de cette facon, j'ai ajouté une nouvelle
declaration "de facon externe" de l'operator<< a la classe std::ostream?

Si cela est exacte comment cela fonctionne t'il?

Merci je patauge un peu.

Par la suite je souhaite declarer deux operateurs << (un pour ecrire
(binaire) dans un fichier, et l'autre pour ecrire (texte) dans la sortie
standard) est ce possible?

10 réponses

1 2 3
Avatar
vandevoorde
Gabriel Dos Reis wrote:
(Fabien Chêne) writes:

| Gabriel Dos Reis writes:
[...]

| > J'avais eu une discussion avec David il y a un certain temps à prop os
| > de cela. Si mes souvenirs sont bons, j'ai compris que la règle
| > actuelle (que je considère bancale) a été taillée sur mesure pour
| > préserver le « Barton-Nackman trick » puisque le bouquin avait un
| > certain succès.



Oui, sauf que je ne pense pas que le succes du livre a lui tout seul
etait
la motivation. Plutot, les participants dans la discussion
consideraient
la technique the Barton et Nackman raisonable (la seule utiliisation de
l'injection d'amis raisonable, en fait) et comme c'etait bien documente
la chance que ce genre de code soit en circulation etait estime grande.



[...]
BTW, pusique nous sommes sur un groupe francophone, tu peux écrire
« David » (vraie orthographe) sans avoir peur qu'il soit prononcé
incorrectement. Il écrit « Daveed » pour forcer la bonne
prononciation.



J'adore l'attention au petit details! (:-)

David

Avatar
Gabriel Dos Reis
writes:

| Gabriel Dos Reis wrote:
| > (Fabien Chêne) writes:
| >
| > | Gabriel Dos Reis writes:
| [...]
| > | > J'avais eu une discussion avec David il y a un certain temps à propos
| > | > de cela. Si mes souvenirs sont bons, j'ai compris que la règle
| > | > actuelle (que je considère bancale) a été taillée sur mesure pour
| > | > préserver le « Barton-Nackman trick » puisque le bouquin avait un
| > | > certain succès.
|
|
| Oui, sauf que je ne pense pas que le succes du livre a lui tout seul
| etait
| la motivation. Plutot, les participants dans la discussion
| consideraient
| la technique the Barton et Nackman raisonable (la seule utiliisation de
| l'injection d'amis raisonable, en fait) et comme c'etait bien documente
| la chance que ce genre de code soit en circulation etait estime grande.

OK, j'ai fait une mise a jour de ma mémoire :-)

| [...]
| > BTW, pusique nous sommes sur un groupe francophone, tu peux écrire
| > « David » (vraie orthographe) sans avoir peur qu'il soit prononcé
| > incorrectement. Il écrit « Daveed » pour forcer la bonne
| > prononciation.
|
|
| J'adore l'attention au petit details! (:-)

C'est important -- surtout quand les sécrétaires insistent à écrire
« Gabby » sur les ordres de mission :-)

-- Gaby
Avatar
fabien.chene
Gabriel Dos Reis writes:

| > BTW, pusique nous sommes sur un groupe francophone, tu peux écrire
| > « David » (vraie orthographe) sans avoir peur qu'il soit prononcé
| > incorrectement. Il écrit « Daveed » pour forcer la bonne
| > prononciation.
|
|
| J'adore l'attention au petit details! (:-)

C'est important -- surtout quand les sécrétaires insistent à écrire
« Gabby » sur les ordres de mission :-)


Tiens, je me demande d'ailleurs comment transformer mon nom pour
éviter que nos amis serbo-croate ne s'abime la gorge en essayant de le
prononcer :-)

--
Fab

Avatar
Sylvain
Bruno CAUSSE wrote on 23/10/2006 15:50:
dans l'article , Fabien LE LEZ à
a écrit le 23/10/06 15:23 :

friend std::ostream& operator<<(std::ostream& stream, const maClass& a);
Note que ce n'est pas forcément une forme conseillée.



Et la forme conseillée est........


je pense que Bruno laissait supposer que "c'est pas bien" de permettre à
une fonction non-membre d'accéder aux données membre (tous les champs
sont private et même la classe ne devraient pas y accéder c'est bien connu!)

personnellement, c'est l'écriture que je préfère -- et je la préfère
notamment à une déclaration non-amie (qui se tapera plein de getXX) hors
de la classe (pour être embétée quand on recherche sa déclaration).

J'aimerai me servir de cet operateur deux fois :

Pour ecrire dans un fichier (binaire) plutot que nommer la fonction write().
Pour ecrire dans la sortie standard plutot que printToString().

Cela est t'il possible?


pas avec ce qui précéde; dans:

friend std::ostream& operator<<(std::ostream& stream, const maClass& a);

il n'y a que le stream ou l'instance 'a' qui peuvent porter ce choix
(porte ouverte n°1); soit vous stocker un modifier dans maClass qui sera
positionné avant injection (porte ouverte n°2) soit vous pouvez mettre
dans ostream un tel flag ou encore récupérer un état caractéristique du
fichier auquel il est (finalement) connecté (il faudrait regarder dans
le détail sa déclaration).

Sylvain.



Avatar
James Kanze
Sylvain wrote:
Bruno CAUSSE wrote on 23/10/2006 15:50:
dans l'article ,
Fabien LE LEZ à a écrit le 23/10/06
15:23 :

friend std::ostream& operator<<(std::ostream& stream, const maClass& a);
Note que ce n'est pas forcément une forme conseillée.



Et la forme conseillée est........


je pense que Bruno laissait supposer que "c'est pas bien" de
permettre à une fonction non-membre d'accéder aux données
membre (tous les champs sont private et même la classe ne
devraient pas y accéder c'est bien connu!)


Je ne sais pas ce que pensait Bruno. Je sais qu'il y a certains
cas où ce n'est pas bien (et je sais que je ne m'en sers pour
ainsi dire jamais moi-même).

La première raison pour l'éviter, évidemment, c'est parce que la
fonction amie ne peut pas être virtuelle ; je crois que pour
certains, c'est une raison absolue, même. La solution
préconcisée, évidemment, c'est une fonction publique virtuelle
print(&std::ostream& ), qui serait appelée par l'opérateur <<.
(C'est la solution que j'adopte en général, mais quand la classe
n'est pas conçue pour servir de classe de base.)

Aussi, il faut se poser la question du but de l'opérator <<. Si
c'est pour le déboggage, la solution amie convient parfaitement.
Si en revanche, c'est pour l'affichage plus général, on pourrait
se poser la question de la logique d'afficher de l'état qui ne
fait pas partie de l'interface visible. Si l'opérateur << n'est
pas ami, aucune risque de cette côté.

Finalement, il y a le fait qu'il faut que la classe connaisse
l'existance de ses amies. Et pourquoi est-ce qu'elle connaître
l'existance d'un formattage textuel, et non celle d'un
formattage XDR, ou d'un formattage BER ? Et où est-ce qu'on
s'arrête ? Il y a de forts arguments que le formattage doit
être indépendant de la classe, que c'est une chose à part. (En
fait, l'encapsulation du formattage pose un problème
fondamentale en soi ; la fonction de formattage dépend en fait
de deux abstractions qui en principe ne doit pas se connaître :
celle de la classe, et celle du format utilisé.)

personnellement, c'est l'écriture que je préfère -- et je la
préfère notamment à une déclaration non-amie (qui se tapera
plein de getXX) hors de la classe (pour être embétée quand on
recherche sa déclaration).


Je ne comprends pas trop. Si une classe a pleine des fonctions
getXXX, c'est probable qu'elle soit mal conçue, au moins si les
XXX correspondent réelement à des membres données. La question
est plus subtile : est-ce qu'on veut afficher de l'état qui
n'est pas signifiant pour l'utilisateur ?

Maintenant, si le but de la déclaration friend n'est que
d'éviter les appels de fonction, pourquoi priviléger operator<<.
Tant que faire, rend les données publiques, et en soit quitte.
Comme ça, tout le monde a les mêmes droits.

J'aimerai me servir de cet operateur deux fois :

Pour ecrire dans un fichier (binaire) plutot que nommer la
fonction write(). Pour ecrire dans la sortie standard
plutot que printToString().

Cela est t'il possible?


pas avec ce qui précéde; dans:

friend std::ostream& operator<<(std::ostream& stream, const maClass& a);

il n'y a que le stream ou l'instance 'a' qui peuvent porter ce choix
(porte ouverte n°1); soit vous stocker un modifier dans maClass qui sera
positionné avant injection (porte ouverte n°2) soit vous pouvez mettre
dans ostream un tel flag ou encore récupérer un état caractéristi que du
fichier auquel il est (finalement) connecté (il faudrait regarder dans
le détail sa déclaration).


C'est la deuxième solution qui est standard. En fait, dans un
monde idéal, la classe MaClasse n'a pas besoin de savoir (et ne
doit pas savoir) les possibilités de formattage qui existe sur
elle.

--
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
Sylvain
James Kanze wrote on 27/10/2006 09:44:

friend std::ostream& operator<<(std::ostream& stream, const maClass& a);
Note que ce n'est pas forcément une forme conseillée.



Et la forme conseillée est........


je pense que Bruno laissait supposer que "c'est pas bien" de
permettre à une fonction non-membre d'accéder aux données
membre (tous les champs sont private et même la classe ne
devraient pas y accéder c'est bien connu!)


Je ne sais pas ce que pensait Bruno. Je sais qu'il y a certains
cas où ce n'est pas bien (et je sais que je ne m'en sers pour
ainsi dire jamais moi-même).


...

La première raison pour l'éviter, évidemment, c'est parce que la
fonction amie ne peut pas être virtuelle ; je crois que pour
certains, c'est une raison absolue, même.


les globales ne sont pas virtuelles ? ni les opérateurs ?
v'la un scoop.

La solution
préconcisée, évidemment, c'est une fonction publique virtuelle
print(&std::ostream& ), qui serait appelée par l'opérateur <<.


c'est ce que je fais sur des classes à dériver avec des outputers
toString(os) et/ou toBin(os) virtuels (pur ou non) protégés (non publics).

sur des structures representant une donnée (simple, ASN, DER,
whatever),j'implémente le traitement dans l'operateur.

(C'est la solution que j'adopte en général, mais quand la classe
n'est pas conçue pour servir de classe de base.)


donc je dirais plutot le contraire, si "base" signfie bien "à dériver".

Aussi, il faut se poser la question du but de l'opérator <<. Si
c'est pour le déboggage, la solution amie convient parfaitement.
Si en revanche, c'est pour l'affichage plus général, on pourrait
se poser la question de la logique d'afficher de l'état qui ne
fait pas partie de l'interface visible. Si l'opérateur << n'est
pas ami, aucune risque de cette côté.


pourquoi "logique de l'affichage" ?? la logique de sérialisation me
semble plus fréquente (soit, c'est projet dépendant).

Finalement, il y a le fait qu'il faut que la classe connaisse
l'existance de ses amies.


?? tu veux dire quoi là ?
il le "faut" à quelle fin ?

Et pourquoi est-ce qu'elle connaître
l'existance d'un formattage textuel, et non celle d'un
formattage XDR, ou d'un formattage BER ? Et où est-ce qu'on
s'arrête ?


assez logiquement on s'arretera à ce qui concerne la classe, si je dumpe
en plain text, ou en XML ou en BER, ou BER-TLV ou DER, la classe et
elle-seule sait comment créer le flux de sortie.

Il y a de forts arguments que le formattage doit
être indépendant de la classe, que c'est une chose à part.


si c'est le nombre de colonnes d'un output texte, le signe de fin de
ligne, je pense en effet que la classe s'en fiche; j'ai aussi
l'impression que ce n'était pas le fond de la question.

En fait, l'encapsulation du formattage pose un problème
fondamentale en soi ; la fonction de formattage dépend en fait
de deux abstractions qui en principe ne doit pas se connaître :
celle de la classe, et celle du format utilisé.)


oui sur le fond, mais il faut le bien gérer, IMHO cela passe par le fait
de donner à la classe les moyens de 'se représenter' selon différement
modes (du dump binaire brut à la très commentée, colorée, identée, ...,
représentation texte).

personnellement, c'est l'écriture que je préfère -- et je la
préfère notamment à une déclaration non-amie (qui se tapera
plein de getXX) hors de la classe (pour être embétée quand on
recherche sa déclaration).


Je ne comprends pas trop. Si une classe a pleine des fonctions
getXXX, c'est probable qu'elle soit mal conçue, au moins si les
XXX correspondent réelement à des membres données. La question
est plus subtile : est-ce qu'on veut afficher de l'état qui
n'est pas signifiant pour l'utilisateur ?


surement pas de "l'état non significatif" mais non-friend priverait
aussi de l'état significatif - qu'il y ait plein de getXX ou un seul ne
change rien (au delà du caractère mal conçu).

Maintenant, si le but de la déclaration friend n'est que
d'éviter les appels de fonction, pourquoi priviléger operator<<.
Tant que faire, rend les données publiques, et en soit quitte.


le but de friend peut être d'accéder à *une partie* de l'état interne
peut décider de quel état-à-externaliser traiter.

friend std::ostream& operator<<(std::ostream& stream, const maClass& a);

il n'y a que le stream ou l'instance 'a' qui peuvent porter ce choix
(porte ouverte n°1); soit vous stocker un modifier dans maClass qui sera
positionné avant injection (porte ouverte n°2) soit vous pouvez mettre
dans ostream un tel flag ou encore récupérer un état caractéristique du
fichier auquel il est (finalement) connecté (il faudrait regarder dans
le détail sa déclaration).


C'est la deuxième solution qui est standard. En fait, dans un
monde idéal, la classe MaClasse n'a pas besoin de savoir (et ne
doit pas savoir) les possibilités de formattage qui existe sur
elle.


il ne s'agissait surement pas tant de formattage (au sens traitement de
texte) que de génération même de l'information.
si je considère un un truc qui est peut être une structure ASN, du DER,
ou encore une structure PKCSx, qui d'autre que la classe a besoin de
connaitre comment formatter le blob interne correctement ? elle seule
sait ce que représente les octets, tout autre partie ne pourra que
dumper un tableau d'octets sans logique apparente (sauf à tout connaitre
de la classe, ce qui serait contre-logique).

Sylvain.





Avatar
James Kanze
Sylvain wrote:
James Kanze wrote on 27/10/2006 09:44:


[...]
sur des structures representant une donnée (simple, ASN, DER,
whatever),j'implémente le traitement dans l'operateur.


Sur les structures représentantes une donnée, je suis tout à
fait d'accord. Mais la question, c'était de faire operator<< un
ami. Sur les structures, ce n'est pas la peine, parce que les
données sont déjà publique.

En fait, ça rejoint un de mes commentaires plus tard : on
pourrait considérer que l'operator<< ne révèle que la structure
« visible » ; qu'il reflette la vue publique de l'objet.

(C'est la solution que j'adopte en général, mais quand la
classe n'est pas conçue pour servir de classe de base.)


donc je dirais plutot le contraire, si "base" signfie bien "à
dériver".


C'est la signification générale. Si la classe est conçue pour
servir de base, je dirais que la solution d'une fonction membre
virtuelle (print, display ou d'autre -- toString n'est en fait
pas forcément une mauvaise idée aujourd'hui) s'impose. Sinon,
c'est une question de style. Une des premières règles de
programmation auxquelles j'ai dû m'adhérer imposer qu'en cas de
surcharge, on définit une fonction nommée, et que l'opérateur ne
faisait que l'appeler. À mon avis, ça va trop loin, surtout dans
le cas des types numériques, mais dans le cas où l'interface
n'est pas un reflet immédiate de l'implémentation (les données
privées), ce n'est pas forcément plus mal.

Aussi, il faut se poser la question du but de l'opérator <<. Si
c'est pour le déboggage, la solution amie convient parfaitement.
Si en revanche, c'est pour l'affichage plus général, on pourrait
se poser la question de la logique d'afficher de l'état qui ne
fait pas partie de l'interface visible. Si l'opérateur << n'est
pas ami, aucune risque de cette côté.


pourquoi "logique de l'affichage" ?? la logique de sérialisation me
semble plus fréquente (soit, c'est projet dépendant).


La sérialisation est un autre problème, et c'est rare (pour
ainsi dire jamais) qu'on sérialise vers un std::ostream. Mais
évidemment, si on utilise un oxdrstream, ou quelque chose de
semblable, c'est aussi une considération. Le problème ici, c'est
que a priori, l'objet ne connaît pas les protocols qui
pourraitent le concerner. (Une exception potentielle, c'est la
persistance. Mais la plupart du temps, dans le cas de la
persistance, l'écriture se fait par un appel à une fonction
membre, et la lecture carrément dans le constructeur. Donc, pas
besoin d'ami là non plus.)

La plupart du temps, aussi, on utilise du code généré pour la
sérialisation.

Finalement, il y a le fait qu'il faut que la classe connaisse
l'existance de ses amies.


?? tu veux dire quoi là ? il le "faut" à quelle fin ?


La déclaration amie ne peut se trouver que dans la définition de
la classe. C'est la règle. Donc, tu ne peux pas ajouter des amis
sans rouvrir la définition de la classe.

Et pourquoi est-ce qu'elle connaître
l'existance d'un formattage textuel, et non celle d'un
formattage XDR, ou d'un formattage BER ? Et où est-ce qu'on
s'arrête ?


assez logiquement on s'arretera à ce qui concerne la classe,
si je dumpe en plain text, ou en XML ou en BER, ou BER-TLV ou
DER, la classe et elle-seule sait comment créer le flux de
sortie.


Le problème, c'est que quand on crée la classe, on ne sait pas
quels flux il faut supporter. Et dans une classe générale, qui
sert dans beaucoup d'applications, ça fait beaucoup de baggage
dans chaque application, dont la plupart du temps, l'application
n'en a pas besoin.

Il y a de forts arguments que le formattage doit
être indépendant de la classe, que c'est une chose à part.


si c'est le nombre de colonnes d'un output texte, le signe de
fin de ligne, je pense en effet que la classe s'en fiche; j'ai
aussi l'impression que ce n'était pas le fond de la question.


Le problème est plus profond que ça. Si c'est de l'XDR, est-ce
que c'est à la classe de savoir que sa représentation doit
occuper une multiple de quatre octets ? En général, chaque
protocol a des contraints particuliers, dont la classe doit
normalement s'en ficher. En revanche, évidemment, chaque classe
a en quelque sort sa propre représentation, dont le protocol
doit être ignorant.

C'est un peu l'idée derrière les opérateurs << et >> de
iostream : ils ne font partie ni de la classe, ni d'istream ou
d'ostream. Et n'ont, en principe, accès qu'à l'interface
publique de chacun. Je dis bien en principe, parce que parfois,
des compromis sont nécessaires.

En fait, l'encapsulation du formattage pose un problème
fondamentale en soi ; la fonction de formattage dépend en fait
de deux abstractions qui en principe ne doit pas se connaître :
celle de la classe, et celle du format utilisé.)


oui sur le fond, mais il faut le bien gérer, IMHO cela passe par le fait
de donner à la classe les moyens de 'se représenter' selon différem ent
modes (du dump binaire brut à la très commentée, colorée, ident ée, ...,
représentation texte).


En principe, pourquoi pas ? Dans la pratique, connais-tu
beaucoup de classes qui le font ? Parce qu'ils font partie de
la norme, beaucoup de classes supportent des entrées/sorties
textuelles sur [io]stream. Mais comme tu viens de nous rappeler,
même « textuelles » ne précise pas vraiment grand chose, et
selon la classe, ces entrées/sorties seront plus ou moins
utiles, selon ce qu'on veut faire. La probabilité que
l'opérateur << définit par la classe convient pour un stream
XML, par exemple, et fort peu, et si par hazard il convient, il
ne conviendra certainement pas pour un affichage dans une
fenêtre sur l'écran.

personnellement, c'est l'écriture que je préfère -- et je la
préfère notamment à une déclaration non-amie (qui se tapera
plein de getXX) hors de la classe (pour être embétée quand on
recherche sa déclaration).


Je ne comprends pas trop. Si une classe a pleine des fonctions
getXXX, c'est probable qu'elle soit mal conçue, au moins si les
XXX correspondent réelement à des membres données. La question
est plus subtile : est-ce qu'on veut afficher de l'état qui
n'est pas signifiant pour l'utilisateur ?


surement pas de "l'état non significatif" mais non-friend priverait
aussi de l'état significatif - qu'il y ait plein de getXX ou un seul ne
change rien (au delà du caractère mal conçu).


En général (et je dis bien en général, parce que je sais qu'il y
a des exceptions), si l'état n'est pas visible de l'interface
publique, il n'est pas signifiante. Aussi, qu'il faut appeler
une fonction getXXX() dans l'operator<< ne me gène pas.

Maintenant, si le but de la déclaration friend n'est que
d'éviter les appels de fonction, pourquoi priviléger operator<<.
Tant que faire, rend les données publiques, et en soit quitte.


le but de friend peut être d'accéder à *une partie* de l'état int erne
peut décider de quel état-à-externaliser traiter.


Si ce cas se présente, je suis d'accord avec toi. Je préfère
personnellement la fonction membre que l'operator<< appelle,
mais c'est une différence de style, et je ne critiquerai pas
quelqu'un qui utilise friend dans ce cas-ci.

Un autre cas, semblable, est quand l'état en question ne se
manifeste qu'indirectement à l'extérieur. Que certaines
fonctions se comportent différemment selon cet état, par
exemple. Là aussi, friend ou une fonction membre s'impose.

Ces motivations sont bien autres que ta première déclaration,
« et je la préfère notamment à une déclaration non-amie (qui se
tapera plein de getXX) hors de la classe (pour être embétée
quand on recherche sa déclaration) ». Je ne rejette pas
l'utilisation de friend per se, mais je veux qu'elle soit
motivée par quelque chose de plus fondamental que « je suis
trop paresseus de taper les getXXX ».

friend std::ostream& operator<<(std::ostream& stream, const maClass& a );

il n'y a que le stream ou l'instance 'a' qui peuvent porter
ce choix (porte ouverte n°1); soit vous stocker un modifier
dans maClass qui sera positionné avant injection (porte
ouverte n°2) soit vous pouvez mettre dans ostream un tel
flag ou encore récupérer un état caractéristique du fichier
auquel il est (finalement) connecté (il faudrait regarder
dans le détail sa déclaration).


C'est la deuxième solution qui est standard. En fait, dans un
monde idéal, la classe MaClasse n'a pas besoin de savoir (et ne
doit pas savoir) les possibilités de formattage qui existe sur
elle.


il ne s'agissait surement pas tant de formattage (au sens
traitement de texte) que de génération même de l'information.


Si on n'affiche que l'état visible à l'interface publique,
l'information est disponible.

Mais comme j'ai dit, « dans un monde idéal ». On n'est pas
dans un monde idéal, et il faut bien faire des compromis.
L'importance, c'est seulement de les reconnaître comme
compromis, et qu'ils soient justifiés par la situation.

si je considère un un truc qui est peut être une structure
ASN, du DER, ou encore une structure PKCSx, qui d'autre que la
classe a besoin de connaitre comment formatter le blob interne
correctement ?


S'il s'agit d'une structure ASN, certes, bien qu'il n'est pas
dit que la classe doit connaître les DER ou les BER. Mais dans
le cas des structures ASN, la question ne se pose pas, de toute
façon, parce que la classe (et ses fonctions de formattage) est
générée par le compilateur ASN. Et ne sert que pour l'interface
ASN. La question se pose, en revanche, si je veux adapter une
classe de l'application pour qu'elle supporte l'ASN.

elle seule sait ce que représente les octets, tout autre
partie ne pourra que dumper un tableau d'octets sans logique
apparente (sauf à tout connaitre de la classe, ce qui serait
contre-logique).


C'est une partie de ce que je suis en train de dire : la classe
ne doit pas connaître BER, mais le flux BER ne doit pas
connaître la classe non plus. C'est le coeur du problème. La
solution iostream, c'est d'utiliser les fonctions globales (les
operator<< et les operator>>), que connaît ni le flux, ni la
classe, qu'en tant qu'utilisateur. Ce n'est pas une mauvaise
solution, mais elle n'est ni parfaite, ni la seule possible non
plus.

--
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
Sylvain
James Kanze wrote on 28/10/2006 15:30:

sur des structures representant une donnée (simple, ASN, DER,
whatever),j'implémente le traitement dans l'operateur.


Sur les structures représentantes une donnée, je suis tout à
fait d'accord. Mais la question, c'était de faire operator<< un
ami. Sur les structures, ce n'est pas la peine, parce que les
données sont déjà publique.


tu es peut être un peu trop manichéen (simple donnée publique vs
structure compliquée multi-membre); on peut avoir affaire à des choses
simples qui ne peuvent pas être publiques (car sensibles ou ne pouvant
être modifiés par l'extérieur) et qui demanderont bien les différentes
représentations dont on discute.

En fait, ça rejoint un de mes commentaires plus tard : on
pourrait considérer que l'operator<< ne révèle que la structure
« visible » ; qu'il reflette la vue publique de l'objet.


oui, ... avec toutefois vues publiques au pluriel.

pourquoi "logique de l'affichage" ?? la logique de sérialisation me
semble plus fréquente (soit, c'est projet dépendant).


La sérialisation est un autre problème, et c'est rare (pour
ainsi dire jamais) qu'on sérialise vers un std::ostream.


je ne pratique pas std::o??stream pour le savoir avoir précision.
j'ai plutôt tendance à utiliser des 'collectors' (au sens accumulateus
de données) typés binaire ou chaîne de caractère basiques (ils ne
traitent que de l'indianess ou du char. set (ASCII7/8, unicode))

Mais évidemment, si on utilise un oxdrstream, ou quelque chose
de semblable, c'est aussi une considération. Le problème ici,
c'est que a priori, l'objet ne connaît pas les protocols qui
pourraient le concerner.


j'ai tendance à privilégier les cas où justement l'objet connait les
protocoles dans lesquels ils s'inscrit.
la motivation est essentiellement pragmatique (je code tel l'objet pour
l'utiliser dans tel cas) et optimiste (je lui fournis tels comportements
car il est pertinent de l'utiliser de telle façon).

une mise en forme unique selon un format XDR est louable en
environnement hétérogène où l'information est fortement distribuée, mais
même ici j'aurais tendance à, un peu, responsabiliser l'objet vis à vis
du format attendu.

(Une exception potentielle, c'est la
persistance. Mais la plupart du temps, dans le cas de la
persistance, l'écriture se fait par un appel à une fonction
membre, et la lecture carrément dans le constructeur. Donc, pas
besoin d'ami là non plus.)


c'est peut être le cas, cela peut être différent; là ça tient pour parti
de conventions subjectives (ayant 'File' une class wrapper de fichier,
il me convient d'écrire: "File(pathname) << anInstance;" comme je n'aime
pas trop les exceptions - pouvant venir d'une lecture invalide - dans un
constructeur).

La plupart du temps, aussi, on utilise du code généré pour la
sérialisation.


généré par quoi, par qui ?
je n'utilise pas ça moi, il faut ?

Finalement, il y a le fait qu'il faut que la classe connaisse
l'existance de ses amies.


?? tu veux dire quoi là ? il le "faut" à quelle fin ?


La déclaration amie ne peut se trouver que dans la définition de
la classe. C'est la règle. Donc, tu ne peux pas ajouter des amis
sans rouvrir la définition de la classe.


bien sur, c'est pour cela que je ne comprenais pas ton point: la classe
connaît forcement ses amies puisque c'est elle qui les définit...

assez logiquement on s'arretera à ce qui concerne la classe[...]


Le problème, c'est que quand on crée la classe, on ne sait pas
quels flux il faut supporter. Et dans une classe générale, qui
sert dans beaucoup d'applications, ça fait beaucoup de baggage
dans chaque application, dont la plupart du temps, l'application
n'en a pas besoin.


d'où ma version optimiste: la classe connait (et donc gère) les
principaux types de flux qui l'utiliseront et sait s'exporter sous ces
formats - reste le flux original qui sera supporté par un opérateur (non
ami) dédié et pourra éventuellement, pour des objets légers, utiliser un
temporaire sur une classe dérivée de la classe à streamer qui piochera
dans les membres protégés (non privés) de celle-ci.

(l'autre approche valide serait de choisir de n'utiliser que des
formatteurs externes à la classe)

Il y a de forts arguments que le formattage doit
être indépendant de la classe, que c'est une chose à part.


si c'est [formatage texte]


Le problème est plus profond que ça. Si c'est de l'XDR, est-ce
que c'est à la classe de savoir que sa représentation doit
occuper une multiple de quatre octets ? En général, chaque
protocol a des contraints particuliers, dont la classe doit
normalement s'en ficher. En revanche, évidemment, chaque classe
a en quelque sort sa propre représentation, dont le protocol
doit être ignorant.


pour un format XDR (et pour toutes contraintes de la couche transport du
protocole), bien sur que cela ne regarde pas la classe.

mais XDR ne servira qu'à transmettre "le contenu de l'objet" (une forme
exhaustive mais minimaliste de son contenu) d'un point à un autre; qui
voudra manipuler l'objet le reconstruira depuis ce flux XDR.

la question initiale (ou peut être la façon dont j'ai voulu la
comprendre) est plutôt de générer des formats applicatifs finaux (non
des formats de transfert), par exemple générer:

'311F301D06045504810016156A616D65732E6B616E7A6540676D61696C2E636F6D'
ou
'5F10' '15' '6A616D65732E6B616E7A6540676D61696C2E636F6D'
ou
<oid="1.2.840.113549.1.9.1" value="" />
ou
email:

(la donnée interne à la classe étant "")

sans devoir être trop déclaratif au niveau de la classe en question, ie
ne pas (forcément) avoir un toBin, toXML, toStr, ..., qui 1) seront
(même avec une vision optimiste) surfait ou lourd, 2) non à même de
gérer le cas du format particulier.

sur ce fond, je revisite souvent mes choix et aucun ne me satisfait
pleinement; une suggestion sur ce point ?

[...] l'opérateur << définit pour un stream XML par exemple
ne conviendra certainement pas pour un affichage dans une
fenêtre sur l'écran.


certes, je pense toutefois que l'on peux limiter les variantes au niveau
de la classe elle-même en refaisant l'hypothèse optimiste que la partie
en charge d'une représentation finale (un affichage concret) sera
capable de comprendre un des formats de sortie de la classe; et en effet
il est "facile" de brancher un flux XML sur une fenêtre utilisateur.

[...]
Ces motivations sont bien autres que ta première déclaration,
« et je la préfère notamment à une déclaration non-amie (qui se
tapera plein de getXX) hors de la classe (pour être embétée
quand on recherche sa déclaration) ». Je ne rejette pas
l'utilisation de friend per se, mais je veux qu'elle soit
motivée par quelque chose de plus fondamental que « je suis
trop paresseus de taper les getXXX ».


c'est que j'ai été aussi paresseux à motiver cet avis que je le serais à
coder des getXX ;)

il ne s'agissait surement pas tant de formattage (au sens
traitement de texte) que de génération même de l'information.


Si on n'affiche que l'état visible à l'interface publique,
l'information est disponible.


oui, dans sa forme brute pas forcément dans sa forme attendue, Cf
exemple ci-avant (interface publique: "char* getEmail()").

La solution iostream, c'est d'utiliser les fonctions globales
(les operator<< et les operator>>), que connaît ni le flux, ni
la classe, qu'en tant qu'utilisateur. Ce n'est pas une mauvaise
solution, mais elle n'est ni parfaite, ni la seule possible non
plus.


tu veux dire utiliser des formatteurs locaux (projet specific)
ostream << SpecificFormatterX(anInstance);
?

Sylvain.



Avatar
James Kanze
Sylvain wrote:
James Kanze wrote on 28/10/2006 15:30:


[../]
En fait, ça rejoint un de mes commentaires plus tard : on
pourrait considérer que l'operator<< ne révèle que la structure
« visible » ; qu'il reflette la vue publique de l'objet.


oui, ... avec toutefois vues publiques au pluriel.


Ça, c'est une très bonne remarque. Une classe peut avoir
plusieurs communités d'utilisateurs, qui voit des choses d'une
façon différente. Il serait facile à dire que dans ce cas-là, on
doit définir une interface (classe abstraite) pour chaque
communité, et en hérite, mais dans la pratique, on ne le fait
pas toujours, même dans les projets les mieux structurées.

pourquoi "logique de l'affichage" ?? la logique de sérialisation me
semble plus fréquente (soit, c'est projet dépendant).


La sérialisation est un autre problème, et c'est rare (pour
ainsi dire jamais) qu'on sérialise vers un std::ostream.


je ne pratique pas std::o??stream pour le savoir avoir précision.
j'ai plutôt tendance à utiliser des 'collectors' (au sens accumulateus
de données) typés binaire ou chaîne de caractère basiques (ils ne
traitent que de l'indianess ou du char. set (ASCII7/8, unicode))


Tout à fait. On collecte les données d'un « enregistrement »,
qu'on écrit d'un coup, au moyen d'une classe spécialisée pour
l'interface/protocol. Si le formattage est texte, il n'est pas
rare que le collecteur est un std::ostringstream, mais même là,
j'ai une tendance à ne créer des ostream que quand j'ai besoin
d'une conversion précise.

Mais évidemment, si on utilise un oxdrstream, ou quelque chose
de semblable, c'est aussi une considération. Le problème ici,
c'est que a priori, l'objet ne connaît pas les protocols qui
pourraient le concerner.


j'ai tendance à privilégier les cas où justement l'objet connait les
protocoles dans lesquels ils s'inscrit.


Ce qui n'est pas forcément faux dans la pratique. Si les types
font partie de l'application, et l'application définit les
protocols qu'elle utilise, c'est même faisable. Ça marche moins
bien pour les types d'une bibliothèque plus générale, qui vise à
servir dans plusieurs applications, qui pourraient se servir des
protocoles non encore connus.

C'est, en tout cas, un compromis. Parfois pratique, mais loin
d'être idéal.

la motivation est essentiellement pragmatique (je code tel
l'objet pour l'utiliser dans tel cas) et optimiste (je lui
fournis tels comportements car il est pertinent de l'utiliser
de telle façon).

une mise en forme unique selon un format XDR est louable en
environnement hétérogène où l'information est fortement
distribuée, mais même ici j'aurais tendance à, un peu,
responsabiliser l'objet vis à vis du format attendu.


Beaucoup dépend de l'application. Dans la téléphonie, je n'ai
pour ainsi dire jamais vu l'XDR -- l'ASN.1/BER était quasiment
la norme, sauf quand on se servait des protocols Internet
prédéfini, genre Radius.

(Une exception potentielle, c'est la
persistance. Mais la plupart du temps, dans le cas de la
persistance, l'écriture se fait par un appel à une fonction
membre, et la lecture carrément dans le constructeur. Donc, pas
besoin d'ami là non plus.)


c'est peut être le cas, cela peut être différent;


Voilà une réponse de normand :-). Qui convient tout à fait. À ce
sujet, je ne crois pas qu'il y a des réponses absolues. J'essaie
simplement à soulever certains des nombreux points à considérer.

là ça tient pour parti de conventions subjectives (ayant
'File' une class wrapper de fichier, il me convient d'écrire:
"File(pathname) << anInstance;" comme je n'aime pas trop les
exceptions - pouvant venir d'une lecture invalide - dans un
constructeur).


Des conventions subjectives, mais aussi des considérations
pragmatiques par rapport à la reste de la conception.

La plupart du temps, aussi, on utilise du code généré pour la
sérialisation.


généré par quoi, par qui ?


Par un programme. Dans le cas de XDR ou des formats ASN.1 (sans
parler de Corba), il en existe même des compilateurs
commerciaux. Sinon, c'est en général très facile d'écrire un
petit script.

je n'utilise pas ça moi, il faut ?


Ça peut réduire énormement le travail. Et la risque des erreurs,
si tu n'as qu'une spécification des éléments (type, etc.) à
maintenir, plutôt qu'une dans la fonction d'écriture, et une
dans la fonction de lecture. Souvent, on peut aussi y ajouter
d'autres fonctionnalités ; la génération des getters et des
setters, si on veut, ou l'initialisation avec des valeurs par
défaut. Où je suis maintenant, on utilise une seule
spécification pour générer la sérialisation pour le protocol de
communication, la sérialisation pour la persistence locale, et
tout ce qui concerne les requêtes dans la base de données : la
structure de la table, les insert et les update, etc. Pour
quatre ou cinq applications différentes, avec des classes
différentes.

[...]

assez logiquement on s'arretera à ce qui concerne la classe[...]


Le problème, c'est que quand on crée la classe, on ne sait pas
quels flux il faut supporter. Et dans une classe générale, qui
sert dans beaucoup d'applications, ça fait beaucoup de baggage
dans chaque application, dont la plupart du temps, l'application
n'en a pas besoin.


d'où ma version optimiste: la classe connait (et donc gère) les
principaux types de flux qui l'utiliseront


Et ma question : est-ce raisonable ? Pour prendre un exemple
simple, dans les applications bancaires, j'utilise souvent une
classe MontantEnDevises (CurrencyAmmount, Betrag), que encapsule
la valeur numérique du montant, et la devise en laquelle il est
calculé. Est-ce réelement raisonable que cette classe
connaissent tous les protocols qui pourraient servir dans le
monde bancaire ? Y compris les protocols maison utilisés à la
Crédit Agricole ? (Ici, les besoins sont un protocol maison, un
format de persistance locale maison, FIX et l'interface avec
SyBase. À la Dresdner Bank, il nous fallait RMI et un protocol
spécifié par IBM qui servait avec CICS.)

[...]
mais XDR ne servira qu'à transmettre "le contenu de l'objet" (une forme
exhaustive mais minimaliste de son contenu) d'un point à un autre; qui
voudra manipuler l'objet le reconstruira depuis ce flux XDR.


Je crois voir où tu veux en venir. Et c'est juste. Seulement, je
ne connais pas de façon réelement intélligente de le faire.
Peut-être une fonction template du genre :

template< typename T >
T& MaClasse::output( T& dest )
{
dest << elem1 ;
dest << elem2 ;
// ...
return dest ;
}

Mais je ne sais pas si j'arrive à le faire marcher pour tous les
formats. Est-ce qu'elle pourrait aussi arriver à traiter des
formats qui veulent des indentificateurs de champs, genre XML ?

Et je n'aime pas introduire un template de ce genre dans une
classe qui n'a autrement pas de templates. Pour des classes un
peu compliquées, d'ailleurs, j'utilise prèsque toujours l'idiome
du pare-feu de compilation, et dans les hiérarchies, évidemment,
il faudrait que les fonctions output soient virtuelles, ce qui
ne va pas non plus.

[...]
sur ce fond, je revisite souvent mes choix et aucun ne me satisfait
pleinement; une suggestion sur ce point ?


Je suis au même point que toi sur ce sujet. Je n'ai pas de
solution qui me satisfait complètement, et je résouds chaque cas
cas par cas. Ici, j'ai soulevé des considérations qui
s'appliquent, mais l'importance ce chaque considération varie
d'un cas à l'autre, et c'est rare qu'elles s'appliquent toutes.

[...]
Ces motivations sont bien autres que ta première déclaration,
« et je la préfère notamment à une déclaration non-amie (qui se
tapera plein de getXX) hors de la classe (pour être embétée
quand on recherche sa déclaration) ». Je ne rejette pas
l'utilisation de friend per se, mais je veux qu'elle soit
motivée par quelque chose de plus fondamental que « je suis
trop paresseus de taper les getXXX ».


c'est que j'ai été aussi paresseux à motiver cet avis que je
le serais à coder des getXX ;)


Mais je crois qu'on finit par se comprendre (une fois n'est pas
coutume). Et même que nos idées cette fois-ci ne sont pas si
éloignées que ça.

il ne s'agissait surement pas tant de formattage (au sens
traitement de texte) que de génération même de l'information.


Si on n'affiche que l'état visible à l'interface publique,
l'information est disponible.


oui, dans sa forme brute pas forcément dans sa forme attendue, Cf
exemple ci-avant (interface publique: "char* getEmail()").

La solution iostream, c'est d'utiliser les fonctions globales
(les operator<< et les operator>>), que connaît ni le flux, ni
la classe, qu'en tant qu'utilisateur. Ce n'est pas une mauvaise
solution, mais elle n'est ni parfaite, ni la seule possible non
plus.


tu veux dire utiliser des formatteurs locaux (projet specific)
ostream << SpecificFormatterX(anInstance);
?


Par exemple. Voire, pour certaines utilisations, s'écarter du
modèle ostream complètement. (Dans le cas de la persistence, par
exemple, je ne vois pas trop ce qu'il nous apporte.)

--
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
Sylvain
James Kanze wrote on 29/10/2006 12:45:

[../]


merci pour tes réponses et pour cet échange constructif - "une fois
n'est pas coutume" ;)

Sylvain.

1 2 3