OVH Cloud OVH Cloud

std et constructeur par copie

7 réponses
Avatar
JM
Bonjour, c'est encore moi :o)

C'est en forgeant qu'on devient forgeron comme disait l'autre, et hier
m'est apparu un petit problème.

Par exemple :

class Bidule
{ Bidule() { }
~Bidule() { delete p ;}
f() { p=new int; }

int *p;
}

Ensuite, j'ajoute un objet dans un vector et j'appelle f().
Puis j'ajoute un nouvel objet avec push-back, par exemple, et là, c'est
le drame...
En loggant, je constante que même lors d'un simple ajout, il y a appel
au destructeur.
Je me suis souvenu des discussions précédentes où des intervenants
expliquaient qu'il y avait création d'objets temporaires.

Je viens donc de modifier ma classe de la manière suivante, en ajoutant
un constructeur par copie :

class Bidule
{ Bidule() { }
Bidule(const Bidule &) {}
~Bidule() { delete p ;}
f() { p=new int; }

int *p;
}

Dans le constructeur par copie, je copie uniquement ce qui m'intéresse
sauf le pointeur p.

A priori, cela semble fonctionner.

J'ai bon où est-ce qu'il y a des subtilités à ajouter?

Merci d'avance

7 réponses

Avatar
Marc Boyer
Le 19-09-2006, JM a écrit :
Par exemple :

class Bidule
{ Bidule() { }
~Bidule() { delete p ;}
f() { p=new int; }

int *p;
}


Pas de mise à NULL du pointeur dans le constructeur ?

Ensuite, j'ajoute un objet dans un vector et j'appelle f().
Puis j'ajoute un nouvel objet avec push-back, par exemple, et là, c'est
le drame...


Qu'appelles-tu 'drame' ?

En loggant, je constante que même lors d'un simple ajout, il y a appel
au destructeur.


Oui. Ben, le paramètre de push_back, faut bien le détruire un jour.

Je me suis souvenu des discussions précédentes où des intervenants
expliquaient qu'il y avait création d'objets temporaires.

Je viens donc de modifier ma classe de la manière suivante, en ajoutant
un constructeur par copie :

class Bidule
{ Bidule() { }
Bidule(const Bidule &) {}
~Bidule() { delete p ;}
f() { p=new int; }

int *p;
}

Dans le constructeur par copie, je copie uniquement ce qui m'intéresse
sauf le pointeur p.


Houlà...

A priori, cela semble fonctionner.


Ben, comme j'ai pas bien compris le pb, je vois pas ce que
corrige la solution.


Marc Boyer
--
Si tu peux supporter d'entendre tes paroles
Travesties par des gueux pour exciter des sots
IF -- Rudyard Kipling (Trad. Paul Éluard)

Avatar
Alain Gaillard

En loggant, je constante que même lors d'un simple ajout, il y a appel
au destructeur.
Je me suis souvenu des discussions précédentes où des intervenants
expliquaient qu'il y avait création d'objets temporaires.


Donc appel à leurs destructeurs en effet.


Dans le constructeur par copie, je copie uniquement ce qui m'intéresse
sauf le pointeur p.


Heu... cette phrase me fait peur.

J'ai bon où est-ce qu'il y a des subtilités à ajouter?


Ca dépend comment on comprend ta phrase ci-dessus.

Montre le code de ton constructeur de copie. Il doit être court donc
facile à montrer ici et comme ça on pourra te dire.

--
Alain

Avatar
JM
Je vais essayer d'être plus clair

class Fenetre
{ Fenetre(int nPar) { pObjet=NULL; Parametre=nPar}
~Fenetre() { delete pObjet ;}
f() { pObjet=new Objet; }

Objet*pObjet;
int Parametre;
}

Ensuite :

vector<Fenetre> tab;

tab.push_back(0);
tab[0].f();

Jusque là, cela fonctionne sans problème.
J'ajoute un nouvel élément :

tab.push_back(255);

C'est là que semble se poser un problème : il y a une copie des objets
Fenetre (à cause du redimensionnement il me semble) puis appel au
destructeur de la copie qui détruit donc mon objet pointé par pObjet.
Si dans tab[0] j'utilise pObjet, bien entendu cela plante puisqu'il
pointe sur un truc détruit.

J'ai donc ajouté :

Fenetre(const Fenetre &src) { Parametre=src.Parametre; pObjet=NULL; }

Sur le coup, cela semble mieux fonctionner, mais j'ai peur que si un
redimensionnement se produit, par exemple, la recopie de mon tableau
contiendra des objets Fenetre avec tous les pointeurs pObjet à NULL
Avatar
Alain Gaillard

Fenetre(const Fenetre &src) { Parametre=src.Parametre; pObjet=NULL; }


C'est bien ce que je craignais....


Sur le coup, cela semble mieux fonctionner, mais j'ai peur que si un
redimensionnement se produit, par exemple, la recopie de mon tableau
contiendra des objets Fenetre avec tous les pointeurs pObjet à NULL


Comme le dit le dicton, la peur n'évite pas le danger :)

Si tu fais une copie tu peux pas faire pOBjet = NULL. Ou alors ce n'est
pas une vraie copie.

Je reprends ton premier code parce que ça sera plus clair je pense.
D'abord comme te l'as dit Marc, tu dois mettre le pointeur p à NULL dès
la construction. Sinon quand le destructeur va être appelé ça va planter
puisque p pointera n'importe où puisque non initialisé.

Voilà le code pour ta classe Bidule:

class Bidule
{
public:
Bidule();
Bidule(const Bidule&);
~Bidule();
void f();
private:
int *p;
};

void Bidule::f()
{
p = new int;
*p = 1;
}

Bidule::Bidule() : p(0)
{}

Bidule::~Bidule()
{
delete p;
}

// constructeur de copie
Bidule::Bidule(const Bidule& src)
{
if(!src.p)
{
p = 0;
return;
}
p = new int;
*p = *src.p;
}


void test()
{
Bidule b1;
// copie avec p NULL
Bidule b2(b1);

b1.f();
// copie avec p non NULL
Bidule b3(b1);
}

int main(int argc, char* argv[])
{
test();

return 0;
}

Maintenant pour le pObjet, il faut le traiter dans le constructeur de
copie. Soit (selon tes besoins) en faisant un simple pObjet = new
Objet(); soit en le copiant lui aussi avec son propre constructeur de
copie et en affectant le résultat dans pObjet :)

--
Alain

Avatar
JM


Si tu fais une copie tu peux pas faire pOBjet = NULL. Ou alors ce n'est
pas une vraie copie.


C'est pas moi qui veut faire la copie, c'est la STL ;-)

Je reprends ton premier code parce que ça sera plus clair je pense.
D'abord comme te l'as dit Marc, tu dois mettre le pointeur p à NULL dès
la construction. Sinon quand le destructeur va être appelé ça va planter
puisque p pointera n'importe où puisque non initialisé.


Oui, j'avais oublié de le mettre dans mon message

// constructeur de copie
Bidule::Bidule(const Bidule& src)
{
if(!src.p)
{
p = 0;
return;
}
p = new int;
*p = *src.p;
}


Ok, c'est pas simple tout ça...

Avatar
loufoque
Je vais essayer d'être plus clair

class Fenetre
{ Fenetre(int nPar) { pObjet=NULL; Parametre=nPar}
~Fenetre() { delete pObjet ;}
f() { pObjet=new Objet; }

Objet*pObjet;


Utilise shared_ptr<Objet> ou clone_ptr<Objet> si tu veux que l'objet
pointé soit copié.

Avatar
Marc Boyer
Le 19-09-2006, JM a écrit :
Je vais essayer d'être plus clair

class Fenetre
{ Fenetre(int nPar) { pObjet=NULL; Parametre=nPar}
~Fenetre() { delete pObjet ;}
f() { pObjet=new Objet; }

Objet*pObjet;
int Parametre;
}

Ensuite :

vector<Fenetre> tab;

tab.push_back(0);
tab[0].f();

Jusque là, cela fonctionne sans problème.
J'ajoute un nouvel élément :

tab.push_back(255);

C'est là que semble se poser un problème : il y a une copie des objets
Fenetre (à cause du redimensionnement il me semble) puis appel au
destructeur de la copie qui détruit donc mon objet pointé par pObjet.
Si dans tab[0] j'utilise pObjet, bien entendu cela plante puisqu'il
pointe sur un truc détruit.

J'ai donc ajouté :

Fenetre(const Fenetre &src) { Parametre=src.Parametre; pObjet=NULL; }

Sur le coup, cela semble mieux fonctionner, mais j'ai peur que si un
redimensionnement se produit, par exemple, la recopie de mon tableau
contiendra des objets Fenetre avec tous les pointeurs pObjet à NULL


En effet.
Dans ton exemple, après le tab.push_back(255), on a tab[0].pObjet = NULL, ce qui n'est surement pas ce que tu veux.

Disons que la stl utilise une sémantique de valeur, et que toi,
ton objet ne l'a pas. Donc, ça plante. Et pour résoudre le pb,
tu introduit un autre pb.

Ce qu'il faut faire, c'est donner une sémantique à la
copie. Si je fais:
Fenetre a(0);
f.f();
Fenetre b( a );
qu'espères-tu avoir comme valeur de a.pObjet et b.pObjet ?

Si tu veux que a et b partagent le même objet, mais que
le dernier vivant le détruise, tu as des shared_ptr<> de boost.
Si tu veux que b pointe sur l'objet mais plus a, tu as
les smart_ptr<>. Si tu veux que b pointe sur un nouvel objet,
copie de celui sur lequel pointait a, il faut écrire le
constructeur de copie et celui d'affectation (avec un idiome
à base de swap pour éviter les redondances de code).


Marc Boyer
--
Si tu peux supporter d'entendre tes paroles
Travesties par des gueux pour exciter des sots
IF -- Rudyard Kipling (Trad. Paul Éluard)