OVH Cloud OVH Cloud

Code expérimental

16 réponses
Avatar
plouf
bonjour,

J'ai fait quelques essais et j'ai eu des
résultats étranges. Je me demande donc si le code
suivant est correct (il est très court):


#include <sstream>
#include <string>
#include <iostream>

using namespace std;

class logger: public stringstream
{
public:

logger() {}

logger& operator()() { return(*this); }

~logger()
{
cout << this << " " << this->str();
cout << endl ;
}
};


int main()
{
logger() << "jjj " << " jjj " << 5 ; // j'ai des doutes ici

logger()() << "jjj " << " jjj " << 5 ; // et ici aussi

logger log;
log << "jjj " << " kkk " << 1 ;
return(0);
}


Résultat : Curieusement g++ me donne le résultat suivant
==========

G++/cygwin :
0x76ed40 0x436000 jjj 5 <== Voici ce que je ne comprends pas
0x76ed40 jjj jjj 5
0x76ed40 jjj kkk 1

VC++6 :
006BFCD4 jjj jjj 5
006BFC48 jjj jjj 5
006BFD60 jjj kkk 1

En plus, ni g++ ni vc++6 ne me mettent de warnings.
Ai-je fait un "Undefined Behaviour" quelque part ?
( Je pense surtout au deux premiers cas, mais le
destructeur semble être appelé suffisamment tard car,
sinon, il n'y aurait aucun affichage ).

10 réponses

1 2
Avatar
Fabien LE LEZ
On Fri, 21 Oct 2005 01:24:20 +0200, plouf :

~logger()
{
cout << this << " " << this->str();
cout << endl ;


Peux-tu garantir que ce code ne lance pas d'exception ?

}
};


int main()
{
logger() << "jjj " << " jjj " << 5 ; // j'ai des doutes ici


Si je ne m'abuse, un temporaire est créé ; il existe pendant toute
l'exécution de l'expression (i.e. jusqu'au point-virgule). Mais je
n'en jurerais pas.

logger()() << "jjj " << " jjj " << 5 ; // et ici aussi


Idem.



J'ai testé sur Comeau ; ça donne :

Comeau C/C++ 4.3.3 (Jan 13 2004 11:29:09) for MS_WINDOWS_x86
Copyright 1988-2003 Comeau Computing. All rights reserved.
MODE:non-strict warnings borland C++

"logger.cpp", line 25: warning: initial value of reference to
non-const must
be an lvalue
logger() << "jjj " << " jjj " << 5 ; // j'ai des doutes ici
^

Et à l'exécution :
1244848 jjj jjj 5
1244848 jjj jjj 5
1244600 jjj kkk 1

Avatar
Jean-Marc Bourguet
plouf writes:

Résultat : Curieusement g++ me donne le résultat suivant
========= >
G++/cygwin :
0x76ed40 0x436000 jjj 5 <== Voici ce que je ne comprends pas


C'est a mon avis un bug de g++ (sur lequel je suis tombe avant hier;
je n'ai pas encore pris le temps de m'assurer qu'il etait connu et
qu'il existait toujours dans les versions encore supportees). Du
moins tous les autres compilateurs que j'ai essaye se comporte comme
VC++.

A+

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

Avatar
kanze
plouf wrote:

J'ai fait quelques essais et j'ai eu des
résultats étranges.


Le code même me semble un peu étrange:-).

Je me demande donc si le code suivant est correct (il est très
court):

#include <sstream>
#include <string>
#include <iostream>

using namespace std;

class logger: public stringstream
{
public:

logger() {}

logger& operator()() { return(*this); }

~logger()
{
cout << this << " " << this->str();
cout << endl ;
}
};


int main()
{
logger() << "jjj " << " jjj " << 5 ; // j'ai des doutes ici


C'est légal, mais ça ne fait pas ce que tu pourrais penser.
L'expression logger() est un temporaire. Il ne peut donc pas
servir à l'initialisation d'une référence. Or, dans les
opérateur << qui sont des fonctions libres, le premier paramètre
est bien une référence vers un non-const. Ils sont donc exclus
de l'ensemble de surcharge. En revanche, on peut appeler une
fonction membre non-const, et les opérateurs << membre sont pris
en considération.

Selon la norme, << char const* n'est pas membre, mais fonction
globale. Il ne fait donc pas partie de l'ensemble considéré pour
la résolution du surcharge. En revanche, un char const* peut se
convertir implicitement en void const*, et l'opérateur << void
const* est, selon la norme, membre. Et étant donné que c'est ici
la seule fonction qu'on peut appelée, c'est ce que doit trouver
la résolution du surcharge. On va donc afficher l'adresse de la
chaîne.

Avec un compilateur strictement conforme. Dans les pratique :

-- Dans les flux classiques, l'opérateur << char const* était
membre. Si tu inclus <iostream.h>, à la place de <ostream>
et al., la résolution du surcharge va donc le trouver, et tu
afficheras bien la chaîne, et non son adresse. (Mais
évidemment, tu n'auras plus stringstream.)

-- Il y a très, très longtemps, il n'était pas interdit
d'initialiser une référence non-const avec un temporaire, et
la plupart des compilateurs, même aujourd'hui, le permet
encore. (La seule exception que je connais, c'est g++.) Avec
un compilateur moderne (disons, post 1995), je m'attendrais
au moins à un avertissement, mais... la résolution du
surcharge va alors trouver le << char const* quand même, et
afficher la chaîne de caractères, et non son adresse.

Le comportement prète à confusion, d'autant plus qu'avec les
flux classiques, certains ont pris l'habitude de sortir une
chaîne vide en tête quand on sortait vers un temporaire, afin
d'obtenir lvalue, e.g. :

logger() << "" << maClasse ;

Ce qui marche aussi avec un compilateur conforme, mais qui
affiche l'adresse de la chaîne vide.

logger()() << "jjj " << " jjj " << 5 ; // et ici aussi


Aucun problème : tu peux appeler une fonction membre, même non
const sur un objet temporaire. Et ton operator() renvoie un
lvalue non const ; il joue le rôle que jouait << "" autrefois.

Tout compte fait :

Les règles de C++ ici sont ce qu'elles sont. Il y a des cas où
elles prètent à confusion, mais elles sont motivées par des
considérations pratiques, et de l'expérience réele. Et moi,
personnellement, même si je n'aime pas la confusion, j'ai dû mal
à voir comment l'ôter sans créer d'autres problèmes plus graves.
En revanche, c'est peut-être un peu dommage que le problème se
présente d'une façon si bizarre dans la bibliothèque standard.
Une solution, peut-être, serait de faire que tous les opérateurs
<< soient des fonctions globales, ce qui fera au moins qu'on les
voit tous, ou aucun, et soit qu'il y a une erreur à la
compilation, soit que le compilateur choisit le surcharge auquel
on s'attend.

Je trouve aussi ton idée d'utiliser l'operator()() pour la
conversion en lvalue assez bonne.

logger log;
log << "jjj " << " kkk " << 1 ;
return(0);
}

Résultat : Curieusement g++ me donne le résultat suivant
==========

G++/cygwin :
0x76ed40 0x436000 jjj 5 <== Voici ce que je ne comprends pas
0x76ed40 jjj jjj 5
0x76ed40 jjj kkk 1

VC++6 :
006BFCD4 jjj jjj 5
006BFC48 jjj jjj 5
006BFD60 jjj kkk 1


C'est g++ qui a raison. Mais comme j'ai dit ci-dessus, il y a
des raisons ici pourquoi peu de compilateur sont conforme.

En plus, ni g++ ni vc++6 ne me mettent de warnings.


C'est plus genant. VC++ doit faire un avertissement, même avec
l'âge qu'il a. (J'ai eu des avertissements dans le cas où je
liais un temporaire à une références déjà en 1992, avec CFront.
Alors, il n'y a vraiment pas d'excuse.)

Ai-je fait un "Undefined Behaviour" quelque part ?


Non, mais tu as trébuché sur un aspect assez subtile de la
norme.

( Je pense surtout au deux premiers cas, mais le destructeur
semble être appelé suffisamment tard car, sinon, il n'y aurait
aucun affichage ).


Dans un compilateur conforme, pas de problème. Et je ne connais
pas de compilateur qui appelle les destructeurs trop tôt par
rapport à la norme. (Sun CC les appelle trop tard, ce qui
ferait que :
logger() << 1 ;
logger() << 2 ;
sortira :
xxxx 2
xxxx 1
à la place de
xxxx 1
xxxx 2
.)

--
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
kanze
Fabien LE LEZ wrote:
On Fri, 21 Oct 2005 01:24:20 +0200, plouf :

~logger()
{
cout << this << " " << this->str();
cout << endl ;


Peux-tu garantir que ce code ne lance pas d'exception ?


Certainement pas. Et alors ?

}
};

int main()
{
logger() << "jjj " << " jjj " << 5 ; // j'ai des doutes ici


Si je ne m'abuse, un temporaire est créé ; il existe pendant
toute l'exécution de l'expression (i.e. jusqu'au
point-virgule). Mais je n'en jurerais pas.


Oui, mais c'est un temporaire. Il ne peut donc pas servir comme
paramètres des opérateurs << globaux. Ce qui va donner une
résolution de surcharge assez inattendue pour la première
chaîne.

logger()() << "jjj " << " jjj " << 5 ; // et ici aussi


Idem.

J'ai testé sur Comeau ; ça donne :

Comeau C/C++ 4.3.3 (Jan 13 2004 11:29:09) for MS_WINDOWS_x86
Copyright 1988-2003 Comeau Computing. All rights reserved.
MODE:non-strict warnings borland C++

"logger.cpp", line 25: warning: initial value of reference to
non-const must
be an lvalue
logger() << "jjj " << " jjj " << 5 ; // j'ai des doutes ici
^

Et à l'exécution :
1244848 jjj jjj 5
1244848 jjj jjj 5
1244600 jjj kkk 1


Ce qui n'est pas conforme:-). (Mais je parie que c'est une
question d'options. Essaie-le avec une mode « strict ». Dans un
compilateur strictement conforme, l'avertissement ne devient pas
une erreur ; il disparaît, mais la résolution du surcharge
change.)

--
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
kanze
Jean-Marc Bourguet wrote:
plouf writes:

Résultat : Curieusement g++ me donne le résultat suivant
==========

G++/cygwin :
0x76ed40 0x436000 jjj 5 <== Voici ce que je ne comprends pas


C'est a mon avis un bug de g++ (sur lequel je suis tombe avant
hier; je n'ai pas encore pris le temps de m'assurer qu'il
etait connu et qu'il existait toujours dans les versions
encore supportees). Du moins tous les autres compilateurs que
j'ai essaye se comporte comme VC++.


Tu n'as pas dû essayé Sun CC. Qui donne :

ffbee030 jjj kkk 1
ffbee0f8 jjj jjj 5
ffbee1c0 jjj jjj 5

(Régarde bien l'ordre des lignes, par rapport à VC++.)

Je m'attendrais à ce que Comeau donne la même chose (plus ou
moins) que g++ en mode strict. Mais il faut bien être en mode
strict.

Selon la norme, c'est g++ qui a raison. Mais il y a de raisons
historiques qui font que la plupart des compilateurs font autre
chose. (Ce qui m'étonne un peu, c'est que Sun CC n'a pas émis un
avertissement. Je m'attendais bien à un message au sujet d'un
"anachronism".)

--
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
Jean-Marc Bourguet
"kanze" writes:

Jean-Marc Bourguet wrote:
plouf writes:

Résultat : Curieusement g++ me donne le résultat suivant
========= >
G++/cygwin :
0x76ed40 0x436000 jjj 5 <== Voici ce que je ne comprends pas


C'est a mon avis un bug de g++ (sur lequel je suis tombe avant
hier; je n'ai pas encore pris le temps de m'assurer qu'il
etait connu et qu'il existait toujours dans les versions
encore supportees). Du moins tous les autres compilateurs que
j'ai essaye se comporte comme VC++.


Tu n'as pas dû essayé Sun CC. Qui donne :

ffbee030 jjj kkk 1
ffbee0f8 jjj jjj 5
ffbee1c0 jjj jjj 5


Je l'ai fait sur mon exemple :-) Maitenant il va falloir que je
cherche la difference entre les deux.

(Régarde bien l'ordre des lignes, par rapport à VC++.)


-features=tmplife

A+

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



Avatar
Jean-Marc Bourguet
"kanze" writes:

Je m'attendrais à ce que Comeau donne la même chose (plus ou
moins) que g++ en mode strict. Mais il faut bien être en mode
strict.


J'ai pas essaye comeau (c'est chez moi que j'en dispose, le probleme
je l'ai trouve au boulot)

Selon la norme, c'est g++ qui a raison. Mais il y a de raisons
historiques qui font que la plupart des compilateurs font autre
chose. (Ce qui m'étonne un peu, c'est que Sun CC n'a pas émis un
avertissement. Je m'attendais bien à un message au sujet d'un
"anachronism".)


Il donne un warning avec +w2 (malheureusement il y a trop de faux
positifs a ce niveau; je ne me serais pas trompe de coupable si
j'avais vu le warning).

Je n'arrive pas a le faire passer dans un mode plus standard, meme
avec
-features=%all,no%extensions,no%iddollar,no%transitions
il ne se comporte pas comme g++ (je m'attendais a ce que le probleme
de binding de reference non constante soit controle avec
no%transition).

A+

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

Avatar
Jean-Marc Bourguet
Jean-Marc Bourguet writes:

"kanze" writes:

Jean-Marc Bourguet wrote:
plouf writes:

Résultat : Curieusement g++ me donne le résultat suivant
========= > >
G++/cygwin :
0x76ed40 0x436000 jjj 5 <== Voici ce que je ne comprends pas


C'est a mon avis un bug de g++ (sur lequel je suis tombe avant
hier; je n'ai pas encore pris le temps de m'assurer qu'il
etait connu et qu'il existait toujours dans les versions
encore supportees). Du moins tous les autres compilateurs que
j'ai essaye se comporte comme VC++.


Tu n'as pas dû essayé Sun CC. Qui donne :

ffbee030 jjj kkk 1
ffbee0f8 jjj jjj 5
ffbee1c0 jjj jjj 5


Je l'ai fait sur mon exemple :-) Maitenant il va falloir que je
cherche la difference entre les deux.
(Régarde bien l'ordre des lignes, par rapport à VC++.)


-features=tmplife


Je n'avais pas compris que la remarque a propos de Sun CC etait sur
l'ordre de destruction de temporaire. Ouf, mon code est bien similaire.

A+

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




Avatar
James Kanze
Jean-Marc Bourguet wrote:
"kanze" writes:


Je m'attendrais à ce que Comeau donne la même chose (plus ou
moins) que g++ en mode strict. Mais il faut bien être en mode
strict.



J'ai pas essaye comeau (c'est chez moi que j'en dispose, le
probleme je l'ai trouve au boulot)


Selon la norme, c'est g++ qui a raison. Mais il y a de raisons
historiques qui font que la plupart des compilateurs font
autre chose. (Ce qui m'étonne un peu, c'est que Sun CC n'a pas
émis un avertissement. Je m'attendais bien à un message au
sujet d'un "anachronism".)



Il donne un warning avec +w2 (malheureusement il y a trop de
faux positifs a ce niveau; je ne me serais pas trompe de
coupable si j'avais vu le warning).


En effet. Surtout sur les fichiers d'en-tête standard:-).

Je n'arrive pas a le faire passer dans un mode plus standard,
meme avec
-features=%all,no%extensions,no%iddollar,no%transitions
il ne se comporte pas comme g++ (je m'attendais a ce que le
probleme de binding de reference non constante soit controle
avec no%transition).


À vrai dire, je n'ai pas régardé côté options. Je sais que Sun
CC offre pas mal de possibilités de ce côté-là.

Il y a aussi des différences de versions. En fait, je ne sais
pas si j'ai essayé avec 5.1 ou 5.5. (J'ai les deux disponible,
selon ce que j'ai mis dans $PATH. Et selon le projet, j'utilise
l'un ou l'autre, ce qui prête à la confusion parfois.)

Enfin, je sais qu'avec la version et les options qu'on utilise
d'habitude, Sun CC ne détruit les temporaires qu'à la fin du
bloc (comme faisait son prédécesseur CFront). Toujours dans
l'ordre inverse de leur création, ce qui fait que la dernière
ligne sort d'abord.

À ma connaissance, c'est le seul compilateur répandu qui n'est
pas conforme à cet égard. Ce qui est frustrant, parce qu'il y a
bien des idiomes (comme ici) qui dépend de la durée de vie des
temporaires.

--
James Kanze mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


Avatar
plouf79

plouf wrote:

J'ai fait quelques essais et j'ai eu des
résultats étranges.


Le code même me semble un peu étrange:-).


C'est normal, c'est expérimental :-D


C'est légal, mais ça ne fait pas ce que tu pourrais penser.
L'expression logger() est un temporaire. Il ne peut donc pas
servir à l'initialisation d'une référence. Or, dans les
opérateur << qui sont des fonctions libres, le premier paramètre
est bien une référence vers un non-const. Ils sont donc exclus
de l'ensemble de surcharge. En revanche, on peut appeler une
fonction membre non-const, et les opérateurs << membre sont pris
en considération.

Selon la norme, << char const* n'est pas membre, mais fonction
globale. Il ne fait donc pas partie de l'ensemble considéré pour
la résolution du surcharge. En revanche, un char const* peut se
convertir implicitement en void const*, et l'opérateur << void
const* est, selon la norme, membre. Et étant donné que c'est ici
la seule fonction qu'on peut appelée, c'est ce que doit trouver
la résolution du surcharge. On va donc afficher l'adresse de la
chaîne.


Merci pour toutes ces précisions


logger()() << "jjj " << " jjj " << 5 ; // et ici aussi




Aucun problème : tu peux appeler une fonction membre, même non
const sur un objet temporaire. Et ton operator() renvoie un
lvalue non const ; il joue le rôle que jouait << "" autrefois.



Ce qui me posait problème ici c'est de renvoyer la référence
vers un objet temporaire. Je me demandait quand l'objet était
detruit et si ce que je faisais respectait bien la norme.

Mais comme l'objet reste "vivant" jusqu'a la fin de la ligne
c'est bon. J'avais pas du tout pensé au problème de surcharge
au début.

Tout compte fait :

Les règles de C++ ici sont ce qu'elles sont. Il y a des cas où
elles prètent à confusion, mais elles sont motivées par des
considérations pratiques, et de l'expérience réele. Et moi,
personnellement, même si je n'aime pas la confusion, j'ai dû mal
à voir comment l'ôter sans créer d'autres problèmes plus graves.
En revanche, c'est peut-être un peu dommage que le problème se
présente d'une façon si bizarre dans la bibliothèque standard.
Une solution, peut-être, serait de faire que tous les opérateurs
<< soient des fonctions globales, ce qui fera au moins qu'on les
voit tous, ou aucun, et soit qu'il y a une erreur à la
compilation, soit que le compilateur choisit le surcharge auquel
on s'attend.

Je trouve aussi ton idée d'utiliser l'operator()() pour la
conversion en lvalue assez bonne.


Merci :-)


1 2