Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Probleme de comprehension : constructeur de copie (templates?)

53 réponses
Avatar
Alex Paris
Bonjour =E0 tous !

Voil=E0 mon petit exemple que je compile en utilisant g++ (cygwin)


#include <iostream>

using namespace std;

template<class T>
class Matrix
{
public:
Matrix(int size);
Matrix(const Matrix<T> &copy);

~Matrix();

Matrix<T>& operator=3D(const Matrix<T>& matrix);

Matrix<T> operator+(const Matrix<T>& second);

private:
int _size;
};

template <class T>
Matrix<T>::Matrix(int size)
: _size(size)
{
cout << "--- Constructor called --- " << endl;
}

template <class T>
Matrix<T>::Matrix(const Matrix<T> &copy)
: _size(copy._size)
{
cout << "--- Copy constructor called --- " << endl;
}

template <class T>
Matrix<T>::~Matrix()
{
cout << "--- Destructor called --- " << endl;
}

template <class T>
Matrix<T>& Matrix<T>::operator=3D(const Matrix<T>& matrix)
{
cout << "--- Operator \"=3D\" called --- " << endl;
_size =3D matrix._size;
}

template <class T>
Matrix<T> Matrix<T>::operator+(const Matrix<T>& second)
{
cout << "--- Operator \"+\" called --- " << endl;
Matrix<T> result(_size);
return result;
}



int main()
{
cout << "Declaration m1" << endl;
Matrix<int> m1(2);
cout << "Declaration m1" << endl;
Matrix<int> m2(2);

cout << "Declaration mSum1" << endl;
Matrix<int> mSum1(2);
cout << "mSum1 =3D m1 + m2" << endl;
mSum1 =3D m1 + m2;

cout << "Declaration mSum2 =3D m1 + m2" << endl;
Matrix<int> mSum2 =3D m1 + m2;

cout << "Declaration mSum3" << endl;
Matrix<int> mSum3(2);
mSum3 =3D m1 + m2;

cout << "Declaration m4 =3D mSum3" << endl;
Matrix<int> m4 =3D mSum3;

int ret;
cin >> ret;

return ret;
}


Comme on voit bien dans le programme en bas la cr=E9ation est appell=E9e
plusieurs fois.

Et voil=E0 la sortie du programme :

$ ./a.exe
Declaration m1
--- Constructor called ---
Declaration m1
--- Constructor called ---
Declaration mSum1
--- Constructor called ---
mSum1 =3D m1 + m2
--- Operator "+" called ---
--- Constructor called ---
--- Operator "=3D" called ---
--- Destructor called ---
Declaration mSum2 =3D m1 + m2
--- Operator "+" called ---
--- Constructor called ---
Declaration mSum3
--- Constructor called ---
--- Operator "+" called ---
--- Constructor called ---
--- Operator "=3D" called ---
--- Destructor called ---
Declaration m4 =3D mSum3
--- Copy constructor called ---
0
--- Destructor called ---
--- Destructor called ---
--- Destructor called ---
--- Destructor called ---
--- Destructor called ---
--- Destructor called ---


Ce que m'int=E9resse le plus c'est pourquoi j'ai ces lignes l=E0 :
Declaration mSum2 =3D m1 + m2
--- Operator "+" called ---
--- Constructor called ---

Je ne vois pas l'operator=3D ni de constructeur de copie. Si je mets le
vrai code pour la classe matrix, le mSum2 est en effet la somme des 2,
l'objet est cr=E9=E9 et vit bien =E7a vie...

Quelqu'un saurait-il m'expliquer la suite des appels ici ? J'imagine
que la ligne
--- Constructor called ---
correspond =E0 la cr=E9ation de l'objet temporaire dans l'operator +. Mais
o=F9 est la trace de l'operator =3D ?



Merci d'avance
A+

10 réponses

1 2 3 4 5
Avatar
Alex Paris
On 6 août, 16:24, Michel Decima wrote:

Et si on la desactive, on voit bien le comportement attendu : un appel
au constructeur par defaut dans operator+, et deux appels au
constructeur de copie : un pour le temporaire resultat de operator+, et
un pour construire mSum2 (comme l'a dit Fabien, c'est bien le
constructeur de copie qui doit etre appelé, meme si on utilise le
signe =).

g++ -fno-elide-constructors matrix.cpp && ./a.out

Declaration mSum2 = m1 + m2
--- Operator "+" called ---
--- Constructor called ---
--- Copy constructor called ---
--- Destructor called ---
--- Copy constructor called ---
--- Destructor called ---

Remarque: dans le cas present, avec des matrices d'une taille
respectable, l'optimisation peut etre vraiment interessante.


Oui par contre : ) reste à savoir pourquoi le constructeur de copie
est appelé 2 fois ?! : )

Je vois bien l'objet : m1 + m2 (imaginons result)
Ensuite il y a une espèce d'opération
Matrix<int> mSum2 = result;
Ensuite on le détruit : result.

Et ? L'objet temporaire est déja détruit ?

Ou en fait il y a une autre copie cachée quand on passe la variable
locale en retour ?! Oui, ça doit être ça. Mais ça je ne peux rien
faire

En fait j'ai essayé d'écrire l'opérateur qui renvoie : Matrix<T> &
mais à chaque fois c'est la cata (une fuite mémoire : l'objet
temporaire n'est jamais détruit)

Avatar
Jean-Marc Bourguet
Alex Paris writes:

On 6 août, 16:24, Michel Decima wrote:

Et si on la desactive, on voit bien le comportement attendu : un appel
au constructeur par defaut dans operator+, et deux appels au
constructeur de copie : un pour le temporaire resultat de operator+, et
un pour construire mSum2 (comme l'a dit Fabien, c'est bien le
constructeur de copie qui doit etre appelé, meme si on utilise le
signe =).

g++ -fno-elide-constructors matrix.cpp && ./a.out

Declaration mSum2 = m1 + m2
--- Operator "+" called ---
--- Constructor called ---
--- Copy constructor called ---
--- Destructor called ---
--- Copy constructor called ---
--- Destructor called ---

Remarque: dans le cas present, avec des matrices d'une taille
respectable, l'optimisation peut etre vraiment interessante.


Oui par contre : ) reste à savoir pourquoi le constructeur de copie
est appelé 2 fois ?! : )


Une premiere fois pour copier la variable result locale a operator+ dans un
temporaire qui contient le resultat de la fonction, une deuxieme fois pour
copier ce temporaire dans mSum2.

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
Fabien LE LEZ
On Tue, 07 Aug 2007 09:17:09 -0700, Alex Paris :

Non mais en réalité l'exemple est super simplifié. Il y a une donnée
_data qui contient tout la structure... le tableu en fait. Si c'est
déclaré T** je ne pense pas que le compilo sera capable de tout
recopier correctement.


En d'autres termes, tu as deux[*] fonctionnalités différentes :
- d'une part, la gestion de mémoire ;
- d'autre part, l'interface de Matrix, i.e. la partie
"mathématiques".

Dans ce cas, il faut deux classes séparées, une pour chaque
fonctionnalité. Une classe de gestion de la mémoire (note que
vector<vector<T> > devrait convenir[**]), et une classe Matrix qui a
un objet membre privé de cette classe "gestion de mémoire".
Ainsi, si la classe "gestion de mémoire" a bien un constructeur de
copie spécial, celui par défaut convient pour Matrix.

J'entends déjà les objections, donc autant que j'y réponde tout de
suite :

- Oui, en théorie, on peut tout mettre dans la même classe. Mais dans
ce cas, tu dois vérifier que toutes les fonctions de Matrix utilisent
le pointeur correctement, et en pratique, c'est fastidieux à
programmer, et très chiant à déboguer.

- Tu ne veux peut-être pas utiliser std::vector<>, pour apprendre à
gérer toi-même la mémoire.
La meilleure méthode de gestion de la mémoire, c'est d'utiliser les
conteneurs de la STL. Quitte à apprendre, autant apprendre tout de
suite les méthodes qui fonctionnent bien.
Par contre, tu peux effectivement vouloir programmer toi-même un
équivalent, histoire de comprendre comment ça marche. Intention
louable, mais AMHA, tu as déjà bien assez à faire avec ta classe
Matrix ; laisse ce deuxième exercice pour un peu plus tard.


------------------

[*] Je dirais même que tu as trois fonctionnalités différentes :

- gestion de la mémoire
- indexation ("la 3è cellule de la 2è ligne correspond à tel
emplacement mémoire")
- gestion des maths (inverser la matrice, ajouter deux matrices, etc.)

Mais comme "vector<vector<T> >" s'occupe des deux premiers, ce n'est
pas la peine, dans un premier temps, de trop se compliquer la vie.



[**] ou "map < pair<int,int>, T >" pour les matrices "presque vides".

Avatar
James Kanze
On Aug 7, 6:17 pm, Alex Paris wrote:
On 6 août, 16:56, Fabien LE LEZ wrote:

On Mon, 06 Aug 2007 06:36:52 -0700, Alex Paris :

template<class T>
class Matrix


Ch'tite remarque en passant : dans du code réel, pour une telle
classe, les constructeurs, destructeur et opérateur de copie sont
inutiles. Ou plus exactement, ceux créés automatiquement par le
compilateur conviennent.
C'est d'ailleurs le cas de la majorité des classes ayant une
sémantique de valeur.


Non mais en réalité l'exemple est super simplifié. Il y a une donn ée
_data qui contient tout la structure... le tableu en fait. Si c'est
déclaré T** je ne pense pas que le compilo sera capable de tout
recopier correctement.


Si tu veux une copie profonde, certainement pas. Même si tu veux
une copie superficielle, je vois mal un constructeur de copie
qui ne fait pas un peu de gestion : comptage des utilisations,
etc.

La taille est stokée dans le _size. Ca peut
être une matrice m, n etc. Plein choses. Je veux peut-être optimiser
moi-même la mémoire...Enfin c'est utile je pense. Ai-je tort ?


Je ne crois pas.

--
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
Christophe Lephay
"James Kanze" a écrit dans le message de news:

On Aug 7, 6:17 pm, Alex Paris wrote:
Enfin c'est utile je pense. Ai-je tort ?
Je ne crois pas.



Tu ne crois pas qu'il ait tort ou tu ne crois pas que ce soit utile ? :)


Avatar
Alex Paris
On 7 août, 19:37, Fabien LE LEZ wrote:
J'entends déjà les objections, donc autant que j'y réponde tout de
suite :

- Oui, en théorie, on peut tout mettre dans la même classe. Mais dans
ce cas, tu dois vérifier que toutes les fonctions de Matrix utilisent
le pointeur correctement, et en pratique, c'est fastidieux à
programmer, et très chiant à déboguer.

- Tu ne veux peut-être pas utiliser std::vector<>, pour apprendre à
gérer toi-même la mémoire.
La meilleure méthode de gestion de la mémoire, c'est d'utiliser les
conteneurs de la STL. Quitte à apprendre, autant apprendre tout de
suite les méthodes qui fonctionnent bien.
Par contre, tu peux effectivement vouloir programmer toi-même un
équivalent, histoire de comprendre comment ça marche. Intention
louable, mais AMHA, tu as déjà bien assez à faire avec ta classe
Matrix ; laisse ce deuxième exercice pour un peu plus tard.

------------------

[*] Je dirais même que tu as trois fonctionnalités différentes :

- gestion de la mémoire
- indexation ("la 3è cellule de la 2è ligne correspond à tel
emplacement mémoire")
- gestion des maths (inverser la matrice, ajouter deux matrices, etc.)

Mais comme "vector<vector<T> >" s'occupe des deux premiers, ce n'est
pas la peine, dans un premier temps, de trop se compliquer la vie.

[**] ou "map < pair<int,int>, T >" pour les matrices "presque vides".


Merci Fabien ! : )

Non il n'y aura pas d'objection ! : )
Je suis tombé sur cet exemple uniquement parce que je suis engagé
d'aider quelqu'un pour son exercice à l'école : ) et ça m'a paru
bizarre de ne pas avoir les traces attendues. Je pensais à une
optimisation g++ mais j'en étais pas tout à fait sûr.

Moi en tant que développeur C++ avec nombreuses années d'expérience je
ne développe PAS ce genre de classes en réel. Il y a d'autres chose à
faire : ) Et les classes comme ça on en trouve dans le STL notamment.
Donc je ne m'amuse surtout pas à réecrire le monde.

Merci quand même

Avatar
Alex Paris
On 8 août, 11:04, "Christophe Lephay"
wrote:
"James Kanze" a écrit dans le message de news:

On Aug 7, 6:17 pm, Alex Paris wrote:

Enfin c'est utile je pense. Ai-je tort ?
Je ne crois pas.



Tu ne crois pas qu'il ait tort ou tu ne crois pas que ce soit utile ? :)


Ecoutez : ) il y a une légère différence dans les 2 problèmes
possibles :

1. Il faut que tu implémentes une classe qui va te SERVIR pour la
gestion de matrices... analyse algèbre matricielle etc etc....
2. Il faut écrire une classe Matrix , template, avec un constructeur
de copie, destructor et opérateur =, implémenter les opérations de +,
- et * pour cette classe et montrer son utilisation pour le int !
(cours d'informatique à l'école)

Alors on peut avoir des longs débats sur comment rendre les matrices
etc super performantes et tout et tout... tandis que le problème n'est
pas là : ) C'est pour avoir la NOTE !



Avatar
Fabien LE LEZ
On Wed, 08 Aug 2007 03:01:58 -0700, Alex Paris :

Alors on peut avoir des longs débats sur comment rendre les matrices
etc super performantes


Il ne s'agit pas de ça, mais plutôt d'utiliser dès le début des
techniques simples permettant de faire un programme qui fonctionne du
premier coup.

Avatar
Michel Decima
On Wed, 08 Aug 2007 03:01:58 -0700, Alex Paris :

Alors on peut avoir des longs débats sur comment rendre les matrices
etc super performantes


Il ne s'agit pas de ça, mais plutôt d'utiliser dès le début des
techniques simples permettant de faire un programme qui fonctionne du
premier coup.


Et puisqu'on est dans un cadre scolaire, montrer qu'on connait ces
techniques devrait permettre d'obtenir une meilleure note...


Avatar
Fabien LE LEZ
On Wed, 08 Aug 2007 12:14:53 +0200, Michel Decima
:

Et puisqu'on est dans un cadre scolaire, montrer qu'on connait ces
techniques devrait permettre d'obtenir une meilleure note...


Encore faut-il que le prof soit suffisamment ouvert, et ça, c'est pas
forcément gagné.
Le premier rôle d'un élève est un travail de psychologie : comprendre
ce que le professeur veut.

1 2 3 4 5