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
Fabien LE LEZ
On Mon, 23 Oct 2006 14:31:45 +0200, Bruno CAUSSE :

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


Note que ce n'est pas forcément une forme conseillée.

La ligne

friend void f();

indique que la fonction libre (i.e. non membre) f(), déclarée plus
haut, est "amie" de la classe, i.e. peut accéder à tous ses membres.

Si la fonction en question n'a pas été déclarée préalablement, elle
est déclarée implicitement dans le namespace global (je crois).

Avatar
Michel Decima

La ligne

friend void f();

indique que la fonction libre (i.e. non membre) f(), déclarée plus
haut, est "amie" de la classe, i.e. peut accéder à tous ses membres.

Si la fonction en question n'a pas été déclarée préalablement, elle
est déclarée implicitement dans le namespace global (je crois).


Non, il faut la declarer explicitement (mais ca a du marcher dans le
passé). Par exemple, pour obtenir le comportement que tu decris avec
g++-4, il faut utilise l'option -ffriend-injection.

Avatar
Bruno CAUSSE
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........


La ligne

friend void f();

indique que la fonction libre (i.e. non membre) f(),


Ok,

déclarée plus haut,


?,

est "amie" de la classe, i.e. peut accéder à tous ses >membres.



Ok

Si la fonction en question n'a pas été déclarée préalablement, elle
est déclarée implicitement dans le namespace global (je crois).


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?


Avatar
kanze
Bruno CAUSSE wrote:

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 maCla ss.


En effet. L'opérateur est declaré dans la classe, et n'est
visible que dans la classe ou par ADL (au moins que tu aies une
autre declaration par ailleurs). En revanche, la declaration
réfère à une fonction dans la portée référentielle qui contient
la classe.

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


C'est la motivation du friend. Bien qu'il arrive que dans le cas
des définitions des fonctions, ce n'est pas la vraie
motivation ; la motivation dans ce cas-là peut être de pouvoir
définir une fonction non-template dans un template de classe.

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?


Plus ou moins. La declaration n'a pas de visibilité global. Si
donc j'écris :

class Doh
{
friend void foo() ;
friend void bar( Doh* ) ;
} ;

int main()
{
foo() ;
bar( 0 ) ;
bar( (Doh*)0 ) ;
return 0 ;
}

le compilateur trouve bar dans le deuxième appel, à cause de
ADL, mais ne trouve pas la fonction dans des deux autres appels.

[...]
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?


Bien sûr. L'un prend std::ostream& comme premier paramètre, est
l'autre une référence à ta classe pour écrire en binaire.

--
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
kanze
Fabien LE LEZ wrote:
On Mon, 23 Oct 2006 14:31:45 +0200, Bruno CAUSSE :

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


Note que ce n'est pas forcément une forme conseillée.


Disons que c'est rarement nécessaire, sauf quand la classe en
question est un template.

La ligne

friend void f();

indique que la fonction libre (i.e. non membre) f(), déclarée
plus haut, est "amie" de la classe, i.e. peut accéder à tous
ses membres.


Pas besoin de la declarer avant. Le nom réfère automatiquement à
une fonction dans l'espace référentiel que contient las
définition de la classe où elle se trouve. Mais la declaration
n'est visible que dans la classe même, ou par le biais du ADL.

Si la fonction en question n'a pas été déclarée préalablement,
elle est déclarée implicitement dans le namespace global (je
crois).


Ce n'est pas tout à fait ça. La déclaration obéit aux règles de
portée habituelle, et n'est visible dans la classe. Le nom
qu'elle déclare, en revanche, est censé être défini dans
l'espace référentiel qui contient la définition de la classe.

C'est un changement de la norme par rapport au C++ classique. En
général, en revanche, tu ne le remarqueras pas ; c'est assez
rare de ne pas avoir des paramètres qui dépendent du type de la
classe, et alors, le ADL trouve la fonction.

--
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
Bruno CAUSSE
dans l'article , kanze à
a écrit le 23/10/06 16:45 :

Plus ou moins. La declaration n'a pas de visibilité global. Si
donc j'écris :

class Doh
{
friend void foo() ;
friend void bar( Doh* ) ;
} ;

int main()
{
foo() ;
bar( 0 ) ;
bar( (Doh*)0 ) ;
return 0 ;
}

le compilateur trouve bar dans le deuxième appel, à cause de
ADL, mais ne trouve pas la fonction dans des deux autres appels.


Comment la fonction libre foo() declaré dans Doh peut etre atteinte?
Impossible?

Je suppose que ADL (Argument Dependent name Lookup) est une résolution "a
tiroir" de la signature. Mais de toute façon trop avancé pour moi. A chaque
fois que kanze me repond (merci a lui) j'ai autant de questions que de
reponses :-)



[...]
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?


Bien sûr. L'un prend std::ostream& comme premier paramètre, est
l'autre une référence à ta classe pour écrire en binaire.


Piges pas :(

il faut que je derive un ostream qui implemente une nouvelle fonction

ostream& operator<<(maClass& a) ?


Avatar
kanze
Bruno CAUSSE wrote:
dans l'article
, kanze à
a écrit le 23/10/06 16:45 :

Plus ou moins. La declaration n'a pas de visibilité global. Si
donc j'écris :

class Doh
{
friend void foo() ;
friend void bar( Doh* ) ;
} ;

int main()
{
foo() ;
bar( 0 ) ;
bar( (Doh*)0 ) ;
return 0 ;
}

le compilateur trouve bar dans le deuxième appel, à cause de
ADL, mais ne trouve pas la fonction dans des deux autres appels.


Comment la fonction libre foo() declaré dans Doh peut etre atteinte?
Impossible?


Je l'aurais dit moi-même. Historiquement, il y avait un
trichement avec l'insertion du nom de friend ; je me rappelle
plus des détails (et ma copie de l'ARM est chez moi), mais en
fait, le compilateur trouvait les trois fonctions. C'est encore
le comportement du Sun CC 5.5 et du g++ 3.4.0.

Pendant la normalisation, cette injection a été supprimée, et à
la place, on a dit que l'ADL régarde dans les classes
impliquées. Je crois qu'on s'était bien rendu compte que le code
qui appelait foo(), ci-dessus, ne marcherait plus, mais il faut
avouer qu'une amie qui ne prend pas de paramètre apparenté à la
classe n'a généralement pas de sens. Le cas du premier appel de
bar est un peu plus subtile, en revanche, et je ne sais pas si
on l'a pris en compte.

Évidemment, en ce qui concerne l'operator<<, les deux solutions
marchent également bien : il y a une référence à la classe,
donc, pas de « null pointer constant », avec son type ambigu.

Je suppose que ADL (Argument Dependent name Lookup) est une
résolution "a tiroir" de la signature. Mais de toute façon
trop avancé pour moi. A chaque fois que kanze me repond (merci
a lui) j'ai autant de questions que de reponses :-)


Ne t'en fais pas. Dans ce cas-ci, j'ai dû faire quelques essais
moi-même. La façon que fonctionne l'ADL est parfois loin d'être
évidente.

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


Bien sûr. L'un prend std::ostream& comme premier paramètre, est
l'autre une référence à ta classe pour écrire en binaire.


Piges pas :(

il faut que je derive un ostream qui implemente une nouvelle fonction

ostream& operator<<(maClass& a) ?


Non. L'abstraction de std::ostream, c'est un formattage texte,
et tu ne peux pas réelement t'en servir pour écrire dans un
fichier binaire. Il faut plutôt que tu développes un nouveau
type de flux, quelque chose, disons, du genre oxdrstream (avec
éventuellement des dérivées comme ofxdrstream). Et que tu le
dotes des operator<< qu'il faut pour formatter selon le format
voulu.

--
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
Lahsen
dans l'article , kanze à
a écrit le 23/10/06 16:45 :

Plus ou moins. La declaration n'a pas de visibilité global. Si
donc j'écris :

class Doh
{
friend void foo() ;
friend void bar( Doh* ) ;
} ;

int main()
{
foo() ;
bar( 0 ) ;
bar( (Doh*)0 ) ;
return 0 ;
}

le compilateur trouve bar dans le deuxième appel, à cause de
ADL, mais ne trouve pas la fonction dans des deux autres appels.


Comment la fonction libre foo() declaré dans Doh peut etre atteinte?
Impossible?

Je suppose que ADL (Argument Dependent name Lookup) est une résolution "a
tiroir" de la signature. Mais de toute façon trop avancé pour moi. A chaque
fois que kanze me repond (merci a lui) j'ai autant de questions que de
reponses :-)


[...]
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?
Bien sûr. L'un prend std::ostream& comme premier paramètre, est

l'autre une référence à ta classe pour écrire en binaire.


Piges pas :(

il faut que je derive un ostream qui implemente une nouvelle fonction

ostream& operator<<(maClass& a) ?

Pour lire et écrire un objet d'une classe on aimerait par exemple écrire:


cin >> monObjet; // lire monObjet à partir de l'entrée standard

On doit surcharger l'opérateur de sortie comme par exemple:

ostream& operator<<( ostream &os, const MaClass &monObjet )
{
//écrire monObjet

return os;
}
Et là on peut faire:

MaClass monObjet(...); // construit monObjet
cout << monObjet << endl; // écrire monObjet dans la sortie standard

On doit aussi surcharger l'opérateur d'entrée comme par exemple:

istream& operator>>( istream &is, MaClass &monObjet )
{
// lire monObjet

return is;
}
Et là on peut faire:
MaClass monObjet(...); // construit monObjet
cin >> monObjet; // lire monObjet à partir de l'entrée standard

Les opérateurs de sortie et d'entrée ne peuvent être membre car
l'opérateur est opérant à gauche. S'il est membre fonction monObjet
serait à gauche de l'opérateur: monObjet << cout; // ceci est confus.

Pour lire et écrire dans un fichier on doit inclure fstream:
#include <fstream>
et on utilise par exmple:

ofstream maSortie("monFichier"); //monFichier est ouvert pour l'ecriture

ifstream monEntrée("monFichier); //monFichier est ouvert pour la lecture



Avatar
fabien.chene
"kanze" writes:

Bruno CAUSSE wrote:
dans l'article
, kanze à
a écrit le 23/10/06 16:45 :

Plus ou moins. La declaration n'a pas de visibilité global. Si
donc j'écris :

class Doh
{
friend void foo() ;
friend void bar( Doh* ) ;
} ;

int main()
{
foo() ;
bar( 0 ) ;
bar( (Doh*)0 ) ;
return 0 ;
}

le compilateur trouve bar dans le deuxième appel, à cause de
ADL, mais ne trouve pas la fonction dans des deux autres appels.


Comment la fonction libre foo() declaré dans Doh peut etre atteinte?
Impossible?


Je l'aurais dit moi-même. Historiquement, il y avait un
trichement avec l'insertion du nom de friend ; je me rappelle
plus des détails (et ma copie de l'ARM est chez moi), mais en
fait, le compilateur trouvait les trois fonctions. C'est encore
le comportement du Sun CC 5.5 et du g++ 3.4.0.


(Gcc/g++ accèpte l'injection de nom friend jusqu'à 4.1.0 exclu)

Pendant la normalisation, cette injection a été supprimée, et à
la place, on a dit que l'ADL régarde dans les classes
impliquées. Je crois qu'on s'était bien rendu compte que le code
qui appelait foo(), ci-dessus, ne marcherait plus, mais il faut
avouer qu'une amie qui ne prend pas de paramètre apparenté à la
classe n'a généralement pas de sens.


Je crois que l'intérêt principal de friend name injection, c'était
d'utiliser conjointement des classes templates. lorsque la classe
template était instanciée, les fonctions friend devenait alors
visible. On avait alors un contrôle de la visibilité de la fonction
friend, via l'instanciation -- même si celle-ci n'avait pas de
paramètres apparentés à la classe.

Le cas du premier appel de bar est un peu plus subtile, en revanche,
et je ne sais pas si on l'a pris en compte.


bar( 0 ); ? Si je ne m'abuse, le type de 0 est int, qui n'a pas de
namespace/classe scope associée. Je ne suis pas sur de saisir la
subtilité (?)


--
Fab



Avatar
kanze
Fabien Chêne wrote:
"kanze" writes:

Bruno CAUSSE wrote:
dans l'article
, kanze à
a écrit le 23/10/06 16:45 :

Plus ou moins. La declaration n'a pas de visibilité global. Si
donc j'écris :

class Doh
{
friend void foo() ;
friend void bar( Doh* ) ;
} ;

int main()
{
foo() ;
bar( 0 ) ;
bar( (Doh*)0 ) ;
return 0 ;
}

le compilateur trouve bar dans le deuxième appel, à cause de
ADL, mais ne trouve pas la fonction dans des deux autres appels.


Comment la fonction libre foo() declaré dans Doh peut etre atteinte?
Impossible?


Je l'aurais dit moi-même. Historiquement, il y avait un
trichement avec l'insertion du nom de friend ; je me rappelle
plus des détails (et ma copie de l'ARM est chez moi), mais en
fait, le compilateur trouvait les trois fonctions. C'est encore
le comportement du Sun CC 5.5 et du g++ 3.4.0.


(Gcc/g++ accèpte l'injection de nom friend jusqu'à 4.1.0 exclu)

Pendant la normalisation, cette injection a été supprimée, et à
la place, on a dit que l'ADL régarde dans les classes
impliquées. Je crois qu'on s'était bien rendu compte que le code
qui appelait foo(), ci-dessus, ne marcherait plus, mais il faut
avouer qu'une amie qui ne prend pas de paramètre apparenté à la
classe n'a généralement pas de sens.


Je crois que l'intérêt principal de friend name injection, c'était
d'utiliser conjointement des classes templates. lorsque la classe
template était instanciée, les fonctions friend devenait alors
visible. On avait alors un contrôle de la visibilité de la fonction
friend, via l'instanciation -- même si celle-ci n'avait pas de
paramètres apparentés à la classe.


Tu parles du trick de Barton-Nackman, je crois. L'injection du
nom de l'ami a existé bien avant les templates. Elle n'est en
général pas nécessaire, dans le sens qu'on peut toujours fournir
une declaration dans la bonne portée, en plus de la declaration
de friend. Ce n'est qu'une commodité, rien de plus. Sauf dans le
cas d'un template où tu définis la fonction amie directement
dans la classe ; tu as alors une fonction (non template) par
instantiation de la classe (template), chose qu'on ne peut
difficilement faire autrement. Et surtout, étant donné l'aspect
ouvert de l'ensemble de ces fonctions, on ne peut pas les
définir (ni declarer) ailleurs.

La solution à base de ADL permet aussi que le trick de
Barton-Nackman fonctionne. Reste qu'il y a des cas où on a
maintenant besoin d'une declaration en dehors de la classe, où
qu'on n'en avait pas besoin avant. Du code (probablement pas
beaucoup) qu'on a cassé, en somme.

Le cas du premier appel de bar est un peu plus subtile, en
revanche, et je ne sais pas si on l'a pris en compte.


bar( 0 ); ? Si je ne m'abuse, le type de 0 est int, qui n'a pas de
namespace/classe scope associée. Je ne suis pas sur de saisir la
subtilité (?)


C'est que typiquement, je ne vais pas faire une fonction amie
qui ne réfère pas à la classe en question quelque part dans ses
paramètres ; je ne vois pas de cas (de tête -- peut-être
qu'ils en existent) comme foo(), ci-dessus. Et que si la
fonction fasse référence à la classe dans ses paramètres, les
paramètres réels que je lui passe le feront aussi, et du coup,
l'ADL entre en jeu, et la fonction sera trouvé. L'exception,
évidemment, c'est si la fonction prend un pointeur, Doh*, et que
je lui passe NULL, c-à-d 0. Dans ce cas-là, il n'y a rien dans
les paramètres réels qui réfère à la classe, et donc, il n'y a
pas de ADL.

Dans quelle mesure ce changement touche des programmes réels, je
n'en sais rien. Mais c'est un changement, et c'est un changement
qui pourrait casser du code. Dans certains cas, il pourrait même
en changer la sémantique, sans avertir.

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




1 2 3