2

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.

Community
  • 1
  • 1
Subway
  • 5,286
  • 11
  • 48
  • 59
  • For formatting: don't use tabs. About the code: is B supposed to derive from A? Otherwise the p2a initialization doesn't seem to make sense. – Useless Dec 12 '12 at 11:36
  • If you mean that it doesn't make sense because B does nothing but initializing A, it's just for the example sake. – Subway Dec 12 '12 at 14:30
  • `p2a(new B)` can only compile if `B*` is convertible to an `A*` ... but in your code, it isn't. Also, `B::B` leaks an instance of `A` anyway. – Useless Dec 12 '12 at 14:43
  • @Useless, B::B doesn't leak an instance of A since A threw an exception for it constructor so A a was never created. See the link on my post. Am I wrong? – Subway Dec 12 '12 at 15:07
  • True, the `A` would only be leaked if it didn't throw ... but that means `B`'s constructor (as written) could only be correct if it never succeeded. – Useless Dec 12 '12 at 15:16
  • all fully contructed member objects contained in that object's class will get their destructor called. which is the case if you throw an exception inside of your constructor, which means all your member data are all fully contructed. the situation where some member data's destructor don't get called is when an exception is thrown in the constructor member initialization list, in this case some member data's destructor won't get called, bcz these member data didn't get contructed in the first place – AlexDan Dec 12 '12 at 15:35
  • Thanks. So in the above example, if I have: `class C { boost::shared_ptr p2b; public: C(): p2b(new B); { } };` will the new B leak? – Subway Dec 12 '12 at 15:58
  • The `B` will not _be_ leaked. However, if it were possible for `B::B` to complete (ie, if `A::A` ever succeeded without throwing), it would leak the created `A`. – Useless Dec 12 '12 at 16:04
  • Why would the B not be leaked, where is the memory allocated by "new B" freed, since p2b's destructor isn't called? – Subway Dec 12 '12 at 18:01

1 Answers1

3

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

Useless
  • 64,155
  • 6
  • 88
  • 132
  • `Assuming you change the code so it compiles`... If he does that, you won't be sure what the code actually does, would you? `;-)` – rubenvb Dec 12 '12 at 15:52
  • You answered my question, thanks. I'm still curious about the case that AlexDan brought up: If an exception is thrown from a call in the constructor member initialization list. Please see my comment below. – Subway Dec 12 '12 at 19:30
  • +1 for the nice reference. It doesn't address the specific situation explicitly though. But it led me to [that article](http://www.bandwidthco.com/whitepapers/programming/c-c++/Exception%20Handling.pdf) which discusses this very issue. BTW, according to what it states, in the case that the exception is thrown at the member-initialization-list stage the destructor of p2b won't be called. If I got it wrong, please correct me. Thanks. – Subway Dec 12 '12 at 19:34
  • Why? didn't the "new B" statement allocate memory already? even though its constructor wasn't called. – Subway Dec 12 '12 at 20:49
  • `new B` fails, so the memory is cleaned up automatically. I've clarified this in the answer, but the linked article also discusses it. – Useless Dec 12 '12 at 20:52
  • OK, This issue is explicitly addressed in [C++ Standard 2003](http://cs.nyu.edu/courses/Fall12/CSCI-GA.2110-001/downloads/C++%20Standard%202003.pdf) at 5.3.4-expr.new/17. It states that indeed in that case the storage will be freed, as you said. Thanks again. And [here is a stackoverflow post](http://stackoverflow.com/questions/4717656/is-memory-allocated-for-an-object-automatically-deleted-if-an-exception-is-throw) discussing it. – Subway Dec 13 '12 at 07:30