Search code examples
c++exceptionconstructorshared-ptr

Uncaught exception at constructor after allocating memory


I've read that awesome summary of Michael Burr regarding a constructor that throws an exception, here: Will the below code cause memory leak in c++

My question is: Is the behavior similar when an exception is thrown by a function called from constructor? i.e. in case that the exception isn't caught where it was thrown and thus it climbs up to the constructor and further to the function that called to the constructor.

I'm especially interested to know what about the member objects contained in that object's class, will their destructors be called? More specifically, in my case it's about a member of type boost::shared_ptr.

Consider the following example:

class A {
    A() { throw some_exception }
};

class B {
    B() { A a = new A(); }
};

class C {
    boost::shared_ptr<B> p2b;
    int i;
    int *pint;
    someclass objsomeclass;
    someclass* psomeclass;

public:
    C() {
        objsomeclass = someclass();
        psomeclass = new someclass();
        pint = new int(); 
        p2b(new B);
    }
};

void foo()
{
    C c();
}

main()
{
    foo();
}

Will the destructor of p2a be called? I'll appreciate if you could point me to an appropriate and reliable resource that covers this case.


Solution

  • Assuming you change the code so it compiles, the destructor of p2a (now p2b) will be called because it was successfully default-constructed. However, it will still hold NULL, because your attempt to reset it in the body of C::C fails.

    The memory allocated by new B will be cleaned up automatically by the stack unwinding process. However, pint and psomeclass will both be leaked, because you're not using RAII for these members.

    To clarify, let's step through the code:

    C::C() {
        objsomeclass = someclass();
    
        psomeclass = new someclass();
        pint = new int();
    
        p2b.reset(new B);
        /* in your code as posted, the call sequence is:
           new B (allocate sizeof B)
             -> B::B
               -> new A (allocate sizeof A)
                 -> A::A which throws
               <- new A failed, so memory is freed
             <- B::B failed (initialized subobjects would be
                destroyed here, but there are none)
           new B failed, so memory is freed
        */
    }
    

    Note that:

    • all members are already default-initialized (because you didn't use the initializer list), so they all get destroyed when the body of C::C unwinds
    • if psomeclass and pint were smart pointers, this would release their dynamically-allocated memory. They aren't, so this is leaked.

    In general, it is better style to use the initializer list and RAII.

    For reference, maybe start with this (very old) article: GOTW 66