OVH Cloud OVH Cloud

besoin lumiere sur une classe cannonique pourtant si basique.

5 réponses
Avatar
giova
C'est marrant le C++ au depard on nage en brasse coulée, 2 mois plus
tard on crois avoir compris énormément de choses,puis encore un peu plus
tard, on se rend compte que l'on ne sait toujours "rien" , que l'on est
toujours en apnée :)

Treve d'intro foireuse, je révise un peu tous mes cours, et je ne
comprend pas quelque chose qui pourtant parrait simple.

Soit je fais une classe cannonique ultra basique :



.h------------------------------------------
class CPoint
{
int x,y;

public:
CPoint();
CPoint(int nx, int ny);
~CPoint();
CPoint(const CPoint& ref);
CPoint& operator = (CPoint & ref);
void Set(int nx, int ny);
void Affiche(void);

};
--------------------------------------------

je ne vais pas gaver ce mail en vous mettant le cpp, disons simplement
que dans chaque fonctions, j'affiche dans la console quelle fonction
vient d'etre lancée.

voici un petit main.cpp de test :

--------------------------------------------
#include "Point.h"
#include <iostream>

using namespace std;

void main(void)
{
CPoint tata = CPoint(1,2);
tata.Affiche();
}

--------------------------------------------
et voici ce que j'obtiens lors de l'execution :

Constructeur avec2 params
X : 1
Y : 2

et la vraiment je ne comprends pas pourquoi j'obtiens ce résultat.

voici ce que j'imaginais a l'origine et qui est mis en cause par le
résultat obtenu :

1)construction d'un objet temporaire de type CPoint (appel du
constructeur avec 2 parametres, ca colle avec le résultat a l'ecran)
2)construction de l'objet tata de type CPoint (appel du constructeur
sans parametres, aie la ca ne colle plus avec le résultat a l'ecran)
3)affectation de l'objet temporaire vers l'objet b (appel de la surgarge
de =, aie la encore rien ne s'affiche)

ce qui est amusant c'est que b se voit bien affecté les valeurs 1 et 2,
donc soit un constructeur de recopie est appellé soit l'opérateur =, le
probleme c'est que pour chacune de ces fonctions, j'ai (comme précisé
plus haut) demander a afficher ce qui se passe dans la console, hors
rien n'apparait comme si c'est l'operateur = par defaut qui etait appellé.

quelqu'un pourrait il m'expliquer le pourquoi de tout ca?

5 réponses

Avatar
Anthony Fleury
giova wrote:

salut,

.h------------------------------------------
class CPoint
{
int x,y;
public:
CPoint();
CPoint(int nx, int ny);
~CPoint();
CPoint(const CPoint& ref);
CPoint& operator = (CPoint & ref);
CPoint& operator= (const CPoint&);

ou alors :
CPoint& operator= (CPoint);

void Set(int nx, int ny);
void Affiche(void);


void Affiche();


};

voici un petit main.cpp de test :

--------------------------------------------
#include "Point.h"
#include <iostream>

using namespace std;

void main(void)
int main()

[ contrairement au C ou le fait de faire int main() voudrait dire que main
peut recevoir un nombre indeterminé de paramètres (en clair on ne précise
rien sur les arguments) le C++ dit que int main() ou alors void Affiche()
signifie que ces deux fonctions ne recoivent aucun paramètres, donc
pourquoi se fatiguer à écrire void ? :-) ]

{
CPoint tata = CPoint(1,2);
tata.Affiche();
}

--------------------------------------------
et voici ce que j'obtiens lors de l'execution :

Constructeur avec2 params
X : 1
Y : 2

et la vraiment je ne comprends pas pourquoi j'obtiens ce résultat.

voici ce que j'imaginais a l'origine et qui est mis en cause par le
résultat obtenu :

1)construction d'un objet temporaire de type CPoint (appel du
constructeur avec 2 parametres, ca colle avec le résultat a l'ecran)
2)construction de l'objet tata de type CPoint (appel du constructeur
sans parametres, aie la ca ne colle plus avec le résultat a l'ecran)
3)affectation de l'objet temporaire vers l'objet b (appel de la surgarge
de =, aie la encore rien ne s'affiche)



Il devrait y avoir les étapes suivantes :

1 - Construction de l'objet tempraire CPoint(1,2);
2 - Appel du constructeur de copie CPoint(const CPoint&);
3 - Destruction de l'objet temporaire

Or dans ce cas, rien dans la norme (à ma connaissance) n'oblige le
compilateur à passer par un objet temporaire et appeler le constructeur de
copie. Il me semble donc qu'en règle général aucun ne le fera. (je viens de
tenter avec g++ en lui enlevant toute optimisation et rien à faire...)

Par contre ceci l'appelera :

CPoint t1(1,2);
CPoint t2 = t1;

Dans ce cas, il y a construction de t1 puis appel du constructeur de copie
pour construire t2.

L'opérateur = ne sera appelé quant à lui que dans le cas :

CPoint t1(1,2), t2;
t2 = t1;

En effet tous les cas précédents sont des cas de construction. Même si tu y
mets un =, on reste dans la construction et c'est le constructeur de copie
qui est appelé.

Dans le cas qui nous interesse ici donc le CPoint t1 = CPoint(1,2); je pense
que c'est juste une optimisation du compilateur qui le fait éviter le
passage par un objet temporaire. Encore une fois je ne connais pas la norme
par coeur mais je ne pense pas qu'un compilateur soit forcé à passer par un
objet temporaire dans ce cas, il ne le fait donc pas. (et je ne crois pas
qu'à l'inverse il y soit obligé)

Le passage par un objet temporaire se fera par contre pour :
CPoint t1;
t1 = CPoint(1,2); (1)

qui aura pour conséquence dans (1) :
1 - Construction de CPoint avec deux paramètres
2 - Appel de operator= (const CPoint&)
3 - Destruction de l'objet temporaire

Bonnes révisions,

Anthony
--
"You could be my unintended, choice to live my life extended
You should be the one I'll always love, I'll be there as soon as I can
But I'm busy mending broken pieces of the life I had before"
(C) Muse - Unintended

Avatar
giova
Anthony Fleury wrote:

giova wrote:

salut,


.h------------------------------------------
class CPoint
{
int x,y;
public:
CPoint();
CPoint(int nx, int ny);
~CPoint();
CPoint(const CPoint& ref);
CPoint& operator = (CPoint & ref);


CPoint& operator= (const CPoint&);
ou alors :
CPoint& operator= (CPoint);


void Set(int nx, int ny);
void Affiche(void);



void Affiche();


};



voici un petit main.cpp de test :

--------------------------------------------
#include "Point.h"
#include <iostream>

using namespace std;

void main(void)


int main()
[ contrairement au C ou le fait de faire int main() voudrait dire que main
peut recevoir un nombre indeterminé de paramètres (en clair on ne précise
rien sur les arguments) le C++ dit que int main() ou alors void Affiche()
signifie que ces deux fonctions ne recoivent aucun paramètres, donc
pourquoi se fatiguer à écrire void ? :-) ]


{
CPoint tata = CPoint(1,2);
tata.Affiche();
}

--------------------------------------------
et voici ce que j'obtiens lors de l'execution :

Constructeur avec2 params
X : 1
Y : 2

et la vraiment je ne comprends pas pourquoi j'obtiens ce résultat.

voici ce que j'imaginais a l'origine et qui est mis en cause par le
résultat obtenu :

1)construction d'un objet temporaire de type CPoint (appel du
constructeur avec 2 parametres, ca colle avec le résultat a l'ecran)
2)construction de l'objet tata de type CPoint (appel du constructeur
sans parametres, aie la ca ne colle plus avec le résultat a l'ecran)
3)affectation de l'objet temporaire vers l'objet b (appel de la surgarge
de =, aie la encore rien ne s'affiche)




Il devrait y avoir les étapes suivantes :

1 - Construction de l'objet tempraire CPoint(1,2);
2 - Appel du constructeur de copie CPoint(const CPoint&);
3 - Destruction de l'objet temporaire

Or dans ce cas, rien dans la norme (à ma connaissance) n'oblige le
compilateur à passer par un objet temporaire et appeler le constructeur de
copie. Il me semble donc qu'en règle général aucun ne le fera. (je viens de
tenter avec g++ en lui enlevant toute optimisation et rien à faire...)

Par contre ceci l'appelera :

CPoint t1(1,2);
CPoint t2 = t1;

Dans ce cas, il y a construction de t1 puis appel du constructeur de copie
pour construire t2.

L'opérateur = ne sera appelé quant à lui que dans le cas :

CPoint t1(1,2), t2;
t2 = t1;

En effet tous les cas précédents sont des cas de construction. Même si tu y
mets un =, on reste dans la construction et c'est le constructeur de copie
qui est appelé.

Dans le cas qui nous interesse ici donc le CPoint t1 = CPoint(1,2); je pense
que c'est juste une optimisation du compilateur qui le fait éviter le
passage par un objet temporaire. Encore une fois je ne connais pas la norme
par coeur mais je ne pense pas qu'un compilateur soit forcé à passer par un
objet temporaire dans ce cas, il ne le fait donc pas. (et je ne crois pas
qu'à l'inverse il y soit obligé)

Le passage par un objet temporaire se fera par contre pour :
CPoint t1;
t1 = CPoint(1,2); (1)

qui aura pour conséquence dans (1) :
1 - Construction de CPoint avec deux paramètres
2 - Appel de operator= (const CPoint&)
3 - Destruction de l'objet temporaire

Bonnes révisions,

Anthony
En effet toutes tes explications semblent justifier le résultat obtenu,

et en fin de compte c'est meme bien que ca se passe comme ca.

y a juste un point qui me chiffone, c'est quand tu dis que :

CPoint t1(1,2), t2; (1)
t2 = t1; (2)

fait appel au constructeur de recopie, car pour moi en (1) il y a appel
du constructeur a 2 param pour t1,et constructeur sans param pour t2
et en (2) il y a appel de la surcharge de =.

je vais d'ailleur de vérifier mes dires via un ptit test et apparemment
je suis dans le bon.

Tout a fait d'accord avec toi sur ton dernier paragraphe concernant :

CPoint t1;
t1 = CPoint(1,2);


c'est d'ailleur confirmé lors de l'execution :

Construction sans param
Constructeur avec2 params
affectation
Destruction

Donc POUR RESUMER, a confirmer tout de meme, mais ca a vraiment l'air
d'etre ca :

il y a une optimisation du compileur,lorsque qu'un objet est défini et
initialisé sur une meme instruction avec pour valeur d'initalisation un
"objet temporaire", le compilateur ne construit pas l'objet temporaire
mais construit directement l'objet souhaité. ainsi on peut dire que :

CPoint t1= CPoint (1,2);
une fois optimisé par le compilo reviens a un :
CPoint t1(1,2);


Avatar
Horst Kraemer
On Sat, 22 May 2004 13:56:30 +0200, giova wrote:

.h------------------------------------------
class CPoint
{
int x,y;

public:
CPoint();
CPoint(int nx, int ny);
~CPoint();
CPoint(const CPoint& ref);
CPoint& operator = (CPoint & ref);


CPoint& operator = (const CPoint & ref);

serait "classique"


void Set(int nx, int ny);
void Affiche(void);

};
--------------------------------------------

je ne vais pas gaver ce mail en vous mettant le cpp, disons simplement
que dans chaque fonctions, j'affiche dans la console quelle fonction
vient d'etre lancée.

voici un petit main.cpp de test :

--------------------------------------------
#include "Point.h"
#include <iostream>

using namespace std;

void main(void)


int main(void)

Même si ton compilateur accepte void main (il a le droit) la norme du
langage C++ permet qu'il y a des compilateurs C++ qui ne l'acceptent
pas. Donc void main n'est pas du C++ portable tandis que tout
compilateur doit accepter int main.

{
CPoint tata = CPoint(1,2);
tata.Affiche();
}

--------------------------------------------
et voici ce que j'obtiens lors de l'execution :

Constructeur avec2 params
X : 1
Y : 2

et la vraiment je ne comprends pas pourquoi j'obtiens ce résultat.

voici ce que j'imaginais a l'origine et qui est mis en cause par le
résultat obtenu :

1)construction d'un objet temporaire de type CPoint (appel du
constructeur avec 2 parametres, ca colle avec le résultat a l'ecran)
2)construction de l'objet tata de type CPoint (appel du constructeur
sans parametres, aie la ca ne colle plus avec le résultat a l'ecran)
3)affectation de l'objet temporaire vers l'objet b (appel de la surgarge
de =, aie la encore rien ne s'affiche)


Tu as décrit l'effet de

CPoint tata;
tata = CPoint(1,2);

ce qui n'est *pas* la même chose que

CPoint tata = CPoint(1,2);

Le '=' dans

CPoint tata = CPoint(1,2);

ne dénote *pas* une affectation. En principe le '=' qui dénote en
général une affectation est "abusé" (on pourrait dire "surchargé")
dans une définition avec initialisation comme signe qui sépare le
cible et la source de l'*initialisation*. Et une initialisation se
fait par un constructeur - ici par une constructeur de copie - et
jamais par un operator=. Donc ce que tu as le droit d'attendre pour

CPoint tata = CPoint(1,2);

serait plutot:

1) Création d'un objet temporaire par CPoint(1,2)
2) Initialisation de tata par le temporaire à l'aide du constructeur
de copie CPoint(const Point&)

donc une action qui correspondrait à l'écriture

CPoint tata(CPoint(1,2));

En fait

CPoint tata(CPoint(1,2));
et
CPoint tata = CPoint(1,2);

sont équivalents par définition du langage C++.

Maintenant il y a une régle qui permet au ccmpilateur de substituer
l'action prescrite théoriquement pour

CPoint tata(CPoint(1,2));

par l'action pour

CPoint tata(1,2);

c.a.d. il a le droit de substituer la séquence théorique par la
création du temporaire "dans tata"

Mais attention. Ce n'est qu'une permission d'optimisation et rien
n'oblige le compilateur d'optimiser. Et puisque ce n'est qu'une
permission d'optimisation et une optimisation de doit jamais affecter
la validité du code il faut toujours que le constructuer de copie soit
accessible si tu écris

CPoint tata = CPoint(1,2);

même si le compilateur ne l'utilise pas parce qu'il optimise. Tu
verras que ton programme ne compile plus si tu mets le constructeur de
copie dans la section privée de la classe

class CPoint
{
int x,y;
CPoint(const CPoint& ref);
public:
CPoint();
CPoint(int nx, int ny);
~CPoint();
CPoint(const CPoint& ref);
CPoint& operator = (const CPoint & ref);
void Set(int nx, int ny);
void Affiche(void);
};


Tu recevras le message

CPoint::CPoint(const CPoint&) is not accessible in function main

bien que le constructeur de copie ne soit pas utilisé "physiquement"
pour

CPoint tata = CPoint(1,2);

par ton compilateur et le seul constructeur utilisé phsiquement est
CPoint(int,int).

--
Horst

Avatar
Anthony Fleury
giova wrote:

Anthony Fleury wrote:

L'opérateur = ne sera appelé quant à lui que dans le cas :

CPoint t1(1,2), t2;
t2 = t1;

En effet tous les cas précédents sont des cas de construction. Même si tu
y mets un =, on reste dans la construction et c'est le constructeur de
copie qui est appelé.



Je me suis mal exprimé ici, je voulais dire que dans tous les cas cité avant
celui là, donc avant ces deux lignes là, le constructeur de copie est
appelé, dans ce cas là, c'est à dire les deux lignes en question, c'est
l'opérateur égal qui est appelé.

[...]

y a juste un point qui me chiffone, c'est quand tu dis que :

CPoint t1(1,2), t2; (1)
t2 = t1; (2)

fait appel au constructeur de recopie, car pour moi en (1) il y a appel
du constructeur a 2 param pour t1,et constructeur sans param pour t2
et en (2) il y a appel de la surcharge de =.



C'est bien ca, je m'étais mal exprimé.

Donc POUR RESUMER, a confirmer tout de meme, mais ca a vraiment l'air
d'etre ca :

il y a une optimisation du compileur,lorsque qu'un objet est défini et
initialisé sur une meme instruction avec pour valeur d'initalisation un
"objet temporaire", le compilateur ne construit pas l'objet temporaire
mais construit directement l'objet souhaité. ainsi on peut dire que :

CPoint t1= CPoint (1,2);
une fois optimisé par le compilo reviens a un :
CPoint t1(1,2);


C'est bien ca, et Horst Kraemer l'a précisé dans son message en expliquant
bien que c'est une règle d'optimisation décrite par la norme (d'ailleurs
comme TC++PL n'en parle pas je ne la connaissais pas spécialement, je ne me
suis pas encore fait une séance lecture de la norme).

Anthony
--
"You could be my unintended, choice to live my life extended
You should be the one I'll always love, I'll be there as soon as I can
But I'm busy mending broken pieces of the life I had before"
(C) Muse - Unintended


Avatar
kanze
giova wrote in message
news:<40af3fd3$0$21564$...

Soit je fais une classe cannonique ultra basique :

.h------------------------------------------
class CPoint
{
int x,y;

public:
CPoint();
CPoint(int nx, int ny);
~CPoint();
CPoint(const CPoint& ref);
CPoint& operator = (CPoint & ref);
void Set(int nx, int ny);
void Affiche(void);

};
--------------------------------------------

je ne vais pas gaver ce mail en vous mettant le cpp, disons simplement
que dans chaque fonctions, j'affiche dans la console quelle fonction
vient d'etre lancée.

voici un petit main.cpp de test :

--------------------------------------------
#include "Point.h"
#include <iostream>

using namespace std;

void main(void)
{
CPoint tata = CPoint(1,2);
tata.Affiche();
}

--------------------------------------------
et voici ce que j'obtiens lors de l'execution :

Constructeur avec2 params
X : 1
Y : 2

et la vraiment je ne comprends pas pourquoi j'obtiens ce résultat.


Tu l'obtiens parce que la norme le permet, par une règle spéciale, et
que c'est plus efficace que la forme formellement voulue.

voici ce que j'imaginais a l'origine et qui est mis en cause par le
résultat obtenu :

1)construction d'un objet temporaire de type CPoint (appel du
constructeur avec 2 parametres, ca colle avec le résultat a l'ecran)


C'est en effet incontournable:-).

2)construction de l'objet tata de type CPoint (appel du constructeur
sans parametres, aie la ca ne colle plus avec le résultat a l'ecran)
3)affectation de l'objet temporaire vers l'objet b (appel de la
surgarge de =, aie la encore rien ne s'affiche)


Malgré les apparences, il n'y a pas d'affectation dans ta ligne. Il n'y
a qu'une initialisation d'objet.

Il y a deux syntaxes d'initialisation :

Type obj ( params ) ;
et
Type obj = quelqueChose ;

La sémantique formelle du premier, c'est d'appeler le constructeur
choisi en fonction des params. la sémantique formelle du seconde, c'est
de convertir « quelqueChose » en type Type, puis appeler le constructeur
de copie (et NON l'opérateur d'affectation) avec le resultat de la
conversion. Mais la norme a une règle spéciale qui permet la suppression
de la copie ici (et dans quelques autres endroits). La justification de
la règle, évidemment, c'est qu'un constructeur de copie doit copier, et
que si on le suppress ici, la différence doit être invisible. (Mais note
bien : la norme n'exige pas que le constructeur ait une sémantique de
copie, et rien d'autre. Elle dit seulement que tu ne peux pas compter
sur ce qu'il soit appelé, même si la sémantique formelle l'exige. Non
seulement c'est légal, mais c'est tout à fait courant d'instrumenter des
constructeurs de copie comme tu as fait.)

ce qui est amusant c'est que b se voit bien affecté les valeurs 1 et
2, donc soit un constructeur de recopie est appellé soit l'opérateur
=, le probleme c'est que pour chacune de ces fonctions, j'ai (comme
précisé plus haut) demander a afficher ce qui se passe dans la
console, hors rien n'apparait comme si c'est l'operateur = par defaut
qui etait appellé.


C'est que le constructeur à deux paramètres est appelé directement sur
tata, sans passage par un intermédiaire.

quelqu'un pourrait il m'expliquer le pourquoi de tout ca?


L'optimisation. On veut que le programme s'execute vite, quitte à ne pas
savoir ce qu'il fait:-). (Sérieusement, je n'ai pas trop de problème
avec l'optimisation ici. Si ton objet supporte la copie, mais il fait
une différence combien de copies il y en a, il y a un problème dans ta
conception. AMHA.)

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