goldcode
goldcode

Reputation: 673

handling failing constructors

I am reading failing constructors from C++ FAQ and don't understand the following code.

void f()
{
  X x;             ← if X::X() throws, the memory for x itself will not leak
  Y* p = new Y();  ← if Y::Y() throws, the memory for *p itself will not leak
}

How is it possible that the memory pointed to by p will not leak if the constructor throws? I assumed the sequence is as follows.

  1. Memory is allocated for object Y.
  2. Y's constructor is called.
  3. Y's constructor throws and memory pointed to by p leaks.

Upvotes: 2

Views: 97

Answers (3)

ComicSansMS
ComicSansMS

Reputation: 54737

It's important to notice that even though the object is deleted, its destructor is not called in this case.

This makes sense, since an exception during construction indicates that the object was never fully constructed (ie. its class invariants have not been established), so calling the destructor could be dangerous.

The downside of this is that if the constructor performs actions that require cleanup which would normally be performed by the destructor, it is now the constructor's responsibility to perform this cleanup in case of exceptions. Take the following example:

class C {
private:
    int* p1;
    int* p2;
public:
    C() : p1(new int()), p2(new int()) {}
    ~C() { delete p1; delete p2; }
};

If the allocation p2 throws, the memory already allocated for p1 will leak. It is your responsibility as a programmer to write constructors in a way that this cannot happen.

The easiest way to achieve this is by delegating the resource management responsibilities to a RAII container class like unique_ptr. That way, no class is responsible for managing more than one resource and scenarios like the one outlined above can no longer occur.

Upvotes: 0

user2249683
user2249683

Reputation:

You will run into a similar problem having a function void f(X*, Y*) and calling f(new X(), new Y()). If one of the new calls succeeds and the other fails you have a memory leak. To resolve it you might create additional functions 'X* make_X()' and 'Y* make_Y()' returning pointers. Now, f(make_X(), make_Y()) is safe. (And after going that far, you might use smart pointers)

Upvotes: 0

Jerry Coffin
Jerry Coffin

Reputation: 490623

If Y's constructor throws, then the stack is unwound, including deleting the memory allocated for Y.

The problems arise primarily when/if you have more than one object to deal with. For example:

void f() { 
    X *x = new X();
    Y *y = new Y();
}

Now, if the new X() part succeeds, but the new Y() part fails, the memory allocated for y will be deleted, but x will not be destroyed, and its memory will be leaked. You can work around this problem with try blocks, if you really insist:

try { 
    X *x = new X();

    Y * y = new Y();
}
catch (y_construction_failed) {
    delete x;
}

The big problem with this is that you have to nest the try blocks if you have more than two items, so if you need, say, half a dozen local variables, it's going to be deeply nested and excruciatingly ugly.

Upvotes: 2

Related Questions