4

Does the following code fragment leak? If not, where do the two objects which are constructed in foobar() get destructed?

class B
{
   int* mpI;

public:
   B() { mpI = new int; }
   ~B() { delete mpI; }
};

void foobar()
{
   B b;

   b = B();  // causes construction
   b = B();  // causes construction
}
Tony Park
  • 1,247
  • 1
  • 9
  • 14

5 Answers5

7

The default copy assignment operator does a member-wise copy.

So in your case:

{
  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.
}

See Item 11 in Effective C++, 2nd Edition for more details.

Alex Budovski
  • 17,947
  • 6
  • 53
  • 58
  • 1
    you need to write a proper, deep copying operator=, or prevent operator= from being called. (good practice unless you explicitly need it) – seand Dec 03 '10 at 00:57
  • 1
    Okay, thanks. I guess this is the reason why they say to define a private assignment operator unless you need assignment... or if I want this to work in any useful way, I need to define an assignment operator. – Tony Park Dec 03 '10 at 01:00
4

Yes, this does leak. The compiler automatically provides an extra method because you have not defined it. The code it generates is equivalent to this:

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

This means that the following stuff happens:

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!

This is obviously bad. This is likely to occur in any instance that you violate the rule of three. You have defined a non-trivial destructor and also a copy constructor. You have not defined a copy assignment, though. The rule of three is that if you define any of the above, you should always define all three.

Instead, do the following:

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
}
SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
3

You're constructing three objects, and all will be destructed. The problem is that the default copy-assignment operator will do a shallow copy. That means the pointer is copied over, which causes it be be deleted more than once. This causes undefined behavior.

This is the reason behind the rule of 3. You have a destructor but not the other two. You need to implement a copy constructor and copy assignment operator, both of which should do a deep copy. This means allocating a new int, copying the value over.

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;
}
Matthew Flaschen
  • 278,309
  • 50
  • 514
  • 539
  • Yep, objects are automatically destroyed once they leave scope. What you (Tony) are doing in your constructor and destructor ensures that no memory shall be leaked when using objects of type B. Now if you made a pointer to an object of type B, you would have to explicitly call B's destructor with `delete bPtr` to avoid leaks. – The Maniac Dec 03 '10 at 00:58
0

As pointed out several times, you've violated the rule of three. Just to add to the links, there is a great discussion of this on stack overflow: What is The Rule of Three?

Community
  • 1
  • 1
pythonic metaphor
  • 10,296
  • 18
  • 68
  • 110
0

Just to offer a different approach to solving the problems of the code I originally posted, I think I could keep the pointer in class B, but take the memory mangement out. Then I need no custom destructor, and so I don't violate the rule of 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;
}
Tony Park
  • 1,247
  • 1
  • 9
  • 14
  • 1
    Good thinking, but there's a big difference here in that the B objects used to (try to) be self-contained, independent objects. They "owned" the int on the heap, and knew it was around as long as they were. With your approach in this answer, B becomes dependent on the lifetime of the data indicated by the pointer passed to the constructor. B can't guarantee the lifetime of that data, and the programmer has to consider it much more carefully. For example, if you create a new B on the heap and return it from your function, then your pI and pJ have been deleted and the B object garbage.... – Tony Delroy Dec 03 '10 at 02:11
  • 1
    (BTW / a third approach is to replace the pointer in your original version with a shared pointer, which will internally provide a safe `operator=()` such that the compiler-generated version for B's `operator=()` and destructor will "just work".) – Tony Delroy Dec 03 '10 at 02:14