OVH Cloud OVH Cloud

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

2 3 4 5 6
Avatar
Michael DOUBEZ
"Alex Paris" a écrit dans le message de news:

Tu utilises STL en disant - voilà fastoche ! Tu n'as pas assisté aux
cours tu arrives à la fin du trimestre et dit que tu sais tout faire.
C'est vite vérifié... Faut pas etre idiot pour comprendre que c'est
pas toi qui as fait le programme


Ouais, sauf qu'on ne parle pas de fonctionnalités avancés mais des
structures de base. Les premiers cours d'un langage, ce sont généralement
les types de données et les tableaux. Un tableau, en c++ contemporain, c'est
un vector.


Faire une matrice en C++ est un peu comme le programme "Hello World".
C'est incroyable le nombre de personnes qui l'ont fait pour apprendre le
C++.

C'est vrai qu'a partir du moment ou tu as fait une matrice, tu a vu les
bases: template sur les données, constructeur, destructeur, overload
d'opérateur, interface public, données privées ou protégées,
spécialization pour la multiplication avec un scalaire ...

Michael


Avatar
Alex Paris
On 8 août, 16:08, Michael DOUBEZ wrote:

Parler de langage a l'état pur fait penser à un clivage langage de
programmation/langage machine. Un langage de programmation utilise des
sortes de librairies: des mécanismes comme les exceptions ou les vtables
dépendent du compilateur par exemple.

Michael


Je suis désolé, la réponse est déjà faite plus haut. J'ai déj à TOUT
compris pour ma question de départ.

alors : en ce qui concerne les librairies malgré le fait que c'était
moi même qui a utilisé ce "terme" le premier veuillez consulter la
réponse de Fabien LE LEZ : c'est un magasin qui vend des livres.
Voilà.

Si on veut bien de la démagogie on peut poursuivre cette discussion en
parlant de plein choses qui n'ont rien à voir avec le sujet. Pourquoi
ne fait-il pas beau ces derniers temps à Paris ? C'est à cause de la
sécheresse, disparition des espèces animales porotégées ou le
rechauffement global ? Qu'est-ce que vous en pensez ?
Eh oui, si on utilise STL on aura le solei enfin ? non ?

Avatar
Alex Paris
On 8 août, 16:11, Fabien LE LEZ wrote:
Exercice dont l'énoncé n'a pas été reproduit ici.
Si tu as besoin d'un tableau dans un programme, la méthode normale est
d'utiliser std::vector<>, sauf si ce n'est pas possible -- soit parce
que son interface ne convient pas, soit parce qu'il est explicitement
demandé de ne pas l'utiliser.

Si le but de l'exercice est d'implémenter soi-même, dans le détail, un
équivalent à std::vector<>, pourquoi pas.

Si le cours est basé sur l'idée qu'implémenter soi-même ce genre de
détails (i.e. des new[] partout) est normal/courant, alors c'est un
cours de C.


Ecoutez, pour cette fois-ci c'était Jean-Marc Bourguet qui a répondu a
ma question.
Vous êtes AUSSI intélligent sans nul doute, alors inutile de parler
des choses évidentes en passant cela pour la réponse de la question
d'origine. Pourquoi le constructeur de copie n'est pas appelé ? NON !
Ce n'est pas parce que je n'ai pas utilisé vector pour la
présentantion des données.

Avatar
Loïc Joly


C'est depuis quand l'utilisation de STL est obligatoire lorsqu'on
parle de C++ ?


Dans le cadre de l'apprentissage, la STL me semble indispensable dans le
sens où elle permet une courbe d'apprentissage progressif, qui peut
apprendre les choses de base (une boucle, par exemple) avant d'apprendre
les choses complexes (la gestion de la mémoire, les constructeurs par
copie et autres fonctions nécessaires lorsqu'on gère la mémoire
manuellement).

Je ne conçois pas un cours où je parlerai de new ou même de class avant
de parler de string et de vector.

Mais lorsqu'on te demande de FAIRE une classe template Matrix qui
gère ... (patati patata... regarde plus haut)... d'après moi il n'est
pas question de montrer tes connaissance en STL.


Si le prof a demandé un exercice mettant en oeuvre la gestion manuelle
de la mémoire, probablement pas. Si c'est un exercice sur la surcharge
d'opérateurs et l'écriture d'algorithmes mathématiques, pourquoi
s'encombrer l'esprit. Et même dans le premier cas, séparer gestion
mémoire du reste en faisant une mini classe vector personnalisée ne me
semble pas une mauvaise idée.


Tu pouvais sinon d'aller cherche les lib motif pour faire une belle
fenêtre, des ascenseurs, les couleurs : )))) etc. Envoyer les
resultats direct sur le mail de prof via un librairie SMTP etc etc.
Mais ce n'est pas le but c'est tout


Et pourquoi pas ? Apprendre à utiliser une bibliothèque existante fait
partie des compétences qu'un développeur doit maîtriser.

--
Loïc

Avatar
Christophe Lephay
"Alex Paris" a écrit dans le message de news:

Si on veut bien de la démagogie on peut poursuivre cette discussion en
parlant de plein choses qui n'ont rien à voir avec le sujet. Pourquoi
ne fait-il pas beau ces derniers temps à Paris ? C'est à cause de la
sécheresse, disparition des espèces animales porotégées ou le
rechauffement global ? Qu'est-ce que vous en pensez ?
Eh oui, si on utilise STL on aura le solei enfin ? non ?


La démagogie consisterait à tenter de faire croire à un débutant qu'il
connait mieux son sujet qu'un expert.

Avatar
James Kanze
On Aug 8, 1:27 pm, Michael DOUBEZ wrote:
"Alex Paris" a écrit dans le message de news:

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ération s 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'e st
pas là : ) C'est pour avoir la NOTE !


La problématique est la même : pour augmenter tes chances d'avoir u ne bonne
note, il faut limiter le risque de faire des erreurs.

Du coup, tu aurais plutot interet à utiliser vector< vector< int > > plutot
qu'un int **.


Un vector de vector est la meilleur façon de tout planter.
Par exemple si un vecteur s'amuse à lancer une exception pendant un
changement de taille, je ne te parle pas de l'état de la matrice.


C'est certainement pas la solution que j'adopterais, non plus.
En revanche, je ne crois pas que les exceptions soient un
problème. Quoiqu'il arrive, on a une garantie faible sur les
données (c'est même un des avantages non négligible des
std::vector) ; on est sûr de pouvoir detruire l'objet, et de
faire le menage. Et franchement, garantir plus pourrait être
très cher si on compte changer les dimensions dynamiquement.

[...]
La méthode classique pour la représentation d'une matrice dense sans
autres caractéristiques (triangulaire, diagonale, creuse ...) est
d'utiliser un tableau unidimensionnel et d'utiliser operator()(int i,
int j){return (i*N+j);} pour acceder aux éléments.


Pour l'utilisation d'un tableau unidimensionnel, je suis tout à
fait d'accord. À mon avis, la seule question qui se pose, c'est
entre std::vector<T> et T*, avec allocation par appelle à la
fonction ::operator new (ou malloc(), mais pas utilisation de
l'opérateur new). Le dernier pourrait être intéressant dans
certains cas d'utilisation, à condition de bien savoir ce qu'on
fait. (Je le déconseillerais fort dans un projet d'école, en
revanche.)

En revanche, il y a bien deux écoles en ce qui concerne
l'interface. Ce que tu suggères suit Barton et Nackman, mais
d'autres ne sont pas de cet avis, et préfère définir
l'operator[] pour renvoyer un proxy, sur lequel l'operator[] est
également défini, ce qui permet l'écriture m[i][j]. (Je crois
que cette deuxième école est plus répandue. Et pour un projet
d'école, je me renseignerais sur l'école à laquelle adhère le
prof, et j'utiliserai celle-là. Ou j'implémenterais les deux,
avec des commentaires qui en expliquent les avantages et les
désavantages de chacun.)

A moins que ce ne soit demander (ou que le prof demande l'utilisation de
POD comme un tableau de valeurs), l'operateur de recopie n'est pas
nécessaire car tout est géré par le compilateur. Et c'est bien de
montrer qu'on sait ça aussi.


Pour une matrice, je ne sais pas. Pour commencer, je crois que
j'écrirais bien un constructeur de copie moi-même. Ne serait-ce
que pour empécher qu'il soit inline, et pour pouvoir
l'instrumenter à mon grè par la suite. Mais je m'arrangerais
surtout d'utiliser des proxy, de façon à ce que les copies
soient rarissimes. (Donc, par exemple, l'operator+ ne renverra
pas une matrice, mais seulement un proxy, qui pourrait servir de
construire une matrice, ou comme opérand d'un autre opérateur.)

Finalement, les opérateurs algébriques ont intérêt à être
définis en dehors de la classe.


Ça dépend, ça aussi. Au moins d'avoir des conversions
implicites, ça dépend de l'implémentation.

Il faut dire que des choses comme Matrix sont assez spéciales.
Elles servent surtout dans le calcul numérique, où on a assez
regulièrement des problèmes de vitesse. Alors, il faut prévoir
dès le départ la possibilité d'une phase d'optimisation. C-à-d
donc une encapsulation au maximum, pour qu'on peut changer tout
en travers s'il faut, sans modifier le code utilisateur. S'il
n'est pas question des conversions implicites, mettre les
opérateurs dans la classe améliore l'encapsulation.

--
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
James Kanze
On Aug 8, 1:50 pm, Fabien LE LEZ wrote:
On Wed, 08 Aug 2007 13:27:52 +0200, Michael DOUBEZ
:

Un vector de vector est la meilleur façon de tout planter.
Par exemple si un vecteur s'amuse à lancer une exception pendant un
changement de taille, je ne te parle pas de l'état de la matrice.


J'ose espérer que c'est du second degré.

(Pour ceux qui n'auraient pas compris : si un bug dans ton programme
fait que vector lance une exception, le même bug aurait des
conséquences bien plus graves (corruption de la mémoire, plantages
aléatoires, etc.) avec un int**.


Il a bien dit « pendant un changement de taille ». Si on veut
supporter le changement dynamique des dimensions de la matrice,
il faut bien accepter la possibilité d'une exception, ne
serait-ce que bad_alloc.

Je crois, d'ailleurs, que son souci soit précisement qu'en cas
d'un bad_alloc, l'invariant myData[i].size() ==
myData[j].size(), pour tout i et j, risque fort de ne plus
valoir. Si on accepte qu'après une exception, la matrice ne
soit plus utilisable (ce qui me semble acceptable), avec les
vecteurs embriqués, on est sûr de pouvoir toujours en appeler le
destructeur, et de pas avoir une fuite de mémoire ou d'autres
incohérences. (Mais ce n'est pas pour autant que ce soit une
bonne solution.)

Je te dis pas l'état de la matrice si une exception est lancée dans la
deuxième ligne d'un code du style
delete[] data;
data= new int [...];
)


Mais qui fait de conneries de ce genre ?

Note par ailleurs qu'il est relativement facile, avec vector<>::swap()
par exemple, de rendre la classe "strongly exception-safe" (i.e. en
cas d'exception, l'objet est dans le même état qu'avant l'appel à la
fonction).


Simple, oui. Mais à quel prix :

void
Matrix<T>::redimension( size_t n, size_t m )
{
Matrix<T> x( *this ) ;
try {
...
} catch ( ... ) {
swap( x ) ;
throw ;
}
} ;

Et est-ce qu'il vaut la peine ?

--
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
James Kanze
On Aug 8, 3:52 pm, Alex Paris wrote:
On 8 août, 14:11, "Christophe Lephay"
wrote:

De mon coté, je n'en serais pas surpris. Le C++ et le C++
enseigné par certains professeur n'ont parfois que le nom en
commun. J'ai déjà rencontré des profs d'info enseignant le
C++ qui n'avaient jamais entendu parlé de vector (c'était il
y a à peu pret 5 ans, les vector n'étaient donc pas une
nouveauté). Dans la foulée, les profs qui ne maitrisent pas
leur sujet sont souvent réfractaires aux notions qu'ils ne
connaissent pas, impossible qu'ils sont d'accepter l'idée
qu'un de leur étudiants puisse connaitre quelque chose
qu'eux-mêmes ignorent.


C'est depuis quand l'utilisation de STL est obligatoire lorsqu'on
parle de C++ ?


Ce n'est pas obligatoire, mais la bibliothèque standard fait
bien partie du langage. Surtout, le type "standard" du tableau,
en C++, c'est bien std::vector. Les autres types (les T[] de C,
std::deque, etc.) ne servent que dans les cas particuliers.
Surtout les T[], qu'on n'utilise pratiquement que pour des
variables de durée de vie statique, afin de permettre une
initialisation statique (ou sinon, quand le profiler dit qu'il
faut).

Je suis d'accord que dans le cadre professionnel il est
fortement conseillé d'utiliser les librairies mais pour les
exercices d'études tu peux très bien te base sur la notion du
langage même en état pur.


Surtout dans les exércises d'étude, le type d'un tableau est
toujours std::vector, dans le C++ bien enseigné. L'utilisation
des T[] est assez complexe, et on ne les présente que quand on
abord les questions d'ordre d'initialisation des objets
statiques. Ou dans un cours de programmation système, où on
apprend à implémenter la bibliothèque standard.

En ce qui concerne cout/cerr et I/O c'est une question de
choix quoi qu'il en soit tu es obligé de montrer tes résultat.


Ce n'est donc pas une question de choix:-).

Mais lorsqu'on te demande de FAIRE une classe template Matrix qui
gère ... (patati patata... regarde plus haut)... d'après moi il n'est
pas question de montrer tes connaissance en STL.


Je ne sais pas. Pour implémenter une classe Matrix, il va bien
falloir un tableau. Et le premier type de tableau que connaitra
un débuttant, c'est bien std::vector.

--
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
Laurent Deniau
On 7 août, 19:37, Fabien LE LEZ wrote:
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".


en realite il y en a au minimum 4 si on veut qqchose de flexible:

- matrice (interface)
- memoire
- structures
- operateurs et fonctionalites

et pour ce qui est de reinventer la roue, les valarray sont un bon
point de depart pour l'implementation des trois derniers points.

a+, ld.


Avatar
Fabien LE LEZ
On Thu, 09 Aug 2007 07:53:52 -0000, James Kanze
:

Quoiqu'il arrive, on a une garantie faible sur les
données (c'est même un des avantages non négligible des
std::vector) ; on est sûr de pouvoir detruire l'objet, et de
faire le menage. Et franchement, garantir plus pourrait être
très cher si on compte changer les dimensions dynamiquement.


Si on veut agrandir progressivement un tableau (push_back), ou le
réduire, alors effectivement il y a un souci de performances.

Par contre, si on veut changer radicalement la taille d'un tableau, il
y a de bonnes chances pour qu'on doive recopier les données de toutes
façons.

Il me semble que ces deux codes ont les mêmes performances :

vector<T> v (12);
v.resize (120);


vector<T> v (12);
vector<T> temp (120);
temp.swap (v);

2 3 4 5 6