2010-12-03 3 views

Répondre

7

L'opérateur d'affectation de copie par défaut effectue une copie par membre.

Donc dans votre cas:

{ 
    B b;  // default construction. 
    b = B(); // temporary is default-contructed, allocating again 
      // copy-assignment copies b.mpI = temp.mpI 
      // b's original pointer is lost, memory is leaked. 
      // temporary is destroyed, calling dtor on temp, which also frees 
      // b's pointer, since they both pointed to the same place. 

    // b now has an invalid pointer. 

    b = B(); // same process as above 

    // at end of scope, b's dtor is called on a deleted pointer, chaos ensues. 
} 

Voir article 11 efficace C++, 2e édition pour plus de détails.

+1

vous devez écrire un opérateur de copie profond approprié, ou empêcher l'opérateur d'être appelé. (bonne pratique sauf si vous en avez explicitement besoin) – seand

+1

Ok, merci. Je suppose que c'est la raison pour laquelle ils disent de définir un opérateur d'affectation privée, sauf si vous avez besoin d'affectation ... ou si je veux que cela fonctionne de manière utile, je dois définir un opérateur d'affectation. –

3

Vous construisez trois objets, et tous seront détruits. Le problème est que l'opérateur d'affectation de copie par défaut effectuera une copie superficielle. Cela signifie que le pointeur est copié, ce qui le fait être supprimé plus d'une fois. Cela provoque un comportement indéfini.

C'est la raison derrière le rule of 3. Vous avez un destructeur mais pas les deux autres. Vous devez implémenter un constructeur de copie et un opérateur d'affectation de copie, qui doivent tous deux effectuer une copie en profondeur. Cela signifie allouer un nouvel int, en copiant la valeur sur.

B(const B& other) : mpI(new int(*other.mpI)) { 
} 

B& operator = (const B &other) { 
    if (this != &other) 
    { 
     int *temp = new int(*other.mpI); 
     delete mpI; 
     mpI = temp; 
    } 
    return *this; 
} 
+0

Oui, les objets sont automatiquement détruits lorsqu'ils quittent la portée. Ce que vous (Tony) faites dans votre constructeur et destructeur garantit qu'aucune fuite ne se produira lors de l'utilisation d'objets de type B. Maintenant, si vous avez fait un pointeur vers un objet de type B, vous devrez explicitement appeler le destructeur de B avec 'delete bPtr' pour éviter les fuites. –

4

Oui, cela entraîne une fuite. Le compilateur fournit automatiquement une méthode supplémentaire car vous ne l'avez pas définie. Le code qu'il génère est équivalent à ceci:

B & B::operator=(const B & other) 
{ 
    mpI = other.mpI; 
    return *this; 
} 

Cela signifie que les choses suivantes se produit:

B b; // b.mpI = heap_object_1 

B temp1; // temporary object, temp1.mpI = heap_object_2 

b = temp1; // b.mpI = temp1.mpI = heap_object_2; heap_object_1 is leaked; 

~temp1(); // delete heap_object_2; b.mpI = temp1.mpI = invalid heap pointer! 

B temp2; // temporary object, temp1.mpI = heap_object_3 

b = temp1; // b.mpI = temp2.mpI = heap_object_3; 

~temp1(); // delete heap_object_3; b.mpI = temp2.mpI = invalid heap pointer! 

~b(); // delete b.mpI; but b.mpI is invalid, UNDEFINED BEHAVIOR! 

Ceci est évidemment mauvais. Cela est susceptible de se produire dans toute instance que vous violez le rule of three. Vous avez défini un destructeur non trivial ainsi qu'un constructeur de copie. Cependant, vous n'avez pas défini d'affectation de copie. La règle de trois est que si vous définissez l'un des éléments ci-dessus, vous devez toujours définir tous les trois.

Au lieu de cela, procédez comme suit:

class B 
{ 
    int* mpI; 

public: 
    B() { mpI = new int; } 
    B(const B & other){ mpI = new int; *mpi = *(other.mpI); } 
    ~B() { delete mpI; } 
    B & operator=(const B & other) { *mpI = *(other.mpI); return *this; } 
}; 

void foobar() 
{ 
    B b; 

    b = B(); // causes construction 
    b = B(); // causes construction 
} 
0

Comme l'a souligné à plusieurs reprises, vous avez violé la règle de trois. Juste pour ajouter aux liens, il y a une grande discussion de ceci sur le débordement de pile: What is The Rule of Three?

0

Juste pour offrir une approche différente pour résoudre les problèmes du code que j'ai d'abord posté, je pense que je pourrais garder le pointeur dans la classe B , mais prenez la gestion de la mémoire. Ensuite, je n'ai pas besoin de destructeur personnalisé, et donc je ne viole pas la règle de 3 ...

class B 
{ 
    int* mpI; 

public: 
    B() {} 
    B(int* p) { mpI = p; } 
    ~B() {} 
}; 

void foobar() 
{ 
    int* pI = new int; 
    int* pJ = new int; 

    B b;  // causes construction 

    b = B(pI); // causes construction 
    b = B(pJ); // causes construction 

    delete pI; 
    delete pJ; 
} 
+1

Bonne idée, mais il y a une grande différence ici en ce que les objets B utilisés pour (essayer de) être des objets autonomes et indépendants. Ils "possédaient" l'int sur le tas, et savaient qu'il était là aussi longtemps qu'ils étaient.Avec votre approche dans cette réponse, B dépend de la durée de vie des données indiquées par le pointeur transmis au constructeur. B ne peut pas garantir la durée de vie de ces données, et le programmeur doit les considérer beaucoup plus attentivement. Par exemple, si vous créez un nouveau B sur le tas et le renvoyez depuis votre fonction, alors vos pI et pJ ont été supprimés et l'objet B est garbage .... –

+1

(BTW/une troisième approche consiste à remplacer le pointeur dans votre Version originale avec un pointeur partagé, qui fournira en interne un opérateur sûr =() 'de sorte que la version générée par le compilateur pour' operator =() 'et destructor de B ne« fonctionne que ». –

Questions connexes