Destructor not invoked when an exception is thrown in the constructor
Asked Answered
H

5

8

Why is the destructor not invoked in this code?

#include <boost/scoped_ptr.hpp>
#include <iostream>

class MyClass {
boost::scoped_ptr<int> ptr;
public:
MyClass() : ptr(new int) { *ptr = 0; throw; std::cout<<"MyClass Allocated\n"; }
~MyClass() { std::cout<<"MyClass De-allocated\n"; }
int increment() { return ++*ptr; }
};

int main()
{
    boost::scoped_ptr<MyClass> myinst(new MyClass);
    std::cout << myinst->increment() << '\n';
    std::cout << myinst->increment() << '\n';
}

EDIT

From the answers, In understand that when an exception happens in the constructor, destructor will not be invoked. But if the exception happens in the main(), ie after the MyClass object is fully instantiated, will the MyClass destructor be invoked? If not, then why it is a smart pointer?

Adding the code

#include <boost/scoped_ptr.hpp>
#include <iostream>

class MyClass {
    boost::scoped_ptr<int> ptr;
    public:
    MyClass() : ptr(new int) { *ptr = 0; std::cout<<"MyClass Allocated\n"; }
    ~MyClass() { std::cout<<"MyClass De-allocated\n"; }
    int increment() { return ++*ptr; }
};

int main()
{
    boost::scoped_ptr<MyClass> myinst(new MyClass);
    throw 3;
    std::cout << myinst->increment() << '\n';
    std::cout << myinst->increment() << '\n';
}

Output:

MyClass Allocated
terminate called after throwing an instance of 'int'
Aborted
Hbeam answered 2/4, 2012 at 6:32 Comment(1)
Do note that throw; here results in undefined behavior. You need to throw something. A throw; statement is only valid in a catch clause, where an exception is already active.Alwyn
P
20

A C++ object's lifetime begins only after its constructor completes successfully.
Since the exception was thrown before constructor call was complete you don't have an complete object and hence no destructor.

Herb Sutter explains this nicely, to quote him:

Q: What does emitting an exception from a constructor mean?

A: It means that construction has failed, the object never existed, its lifetime never began. Indeed, the only way to report the failure of construction -- that is, the inability to correctly build a functioning object of the given type -- is to throw an exception. (Yes, there is a now-obsolete programming convention that said, "if you get into trouble just set a status flag to 'bad' and let the caller check it via an IsOK() function." I'll comment on that presently.)

In biological terms,
conception took place -- the constructor began -- but despite best efforts it was followed by a miscarriage -- the constructor never ran to term(ination).

Incidentally, this is why a destructor will never be called if the constructor didn't succeed -- there's nothing to destroy. "It cannot die, for it never lived." Note that this makes the phrase "an object whose constructor threw an exception" really an oxymoron. Such a thing is even less than an ex-object... it never lived, never was, never breathed its first. It is a non-object.

We might summarize the C++ constructor model as follows:

Either:

(a) The constructor returns normally by reaching its end or a return statement, and the object exists.

Or:

(b) The constructor exits by emitting an exception, and the object not only does not now exist, but never existed as an object.

EDIT 1:
But if the exception happens in the main(), ie after the MyClass object is fully instantiated, will the MyClass destructor be invoked?

Yes, it will be!
That is the purpose of using scoped_ptr, Once an exception is thrown in main, Stack Unwinding would cause all local objects to be deallocated, this means that myinst(which resides on stack) will also be deallocated, which in turn will call the destructor of MyClass.

Refer the Boost doccumentation when in doubt:

The scoped_ptr class template stores a pointer to a dynamically allocated object. (Dynamically allocated objects are allocated with the C++ new expression.) The object pointed to is guaranteed to be deleted, either on destruction of the scoped_ptr, or via an explicit reset

EDIT 2:
Why does your edited program crash?
Your program shows crashes because, You throw an exception but you never catch it. when such a scenario occurs an special function called terminate() is called whose default behavior is to call abort().It is implementation defined behavior whether stack is Unwound before terminate() is called in this particular scenarioRef 1.Seems your implementation doesn't & you should not rely on this behavior as well.

You can modify your program as follows to handle the exception and you should get the behavior you were expecting:

#include <boost/scoped_ptr.hpp> 
#include <iostream> 

class MyClass { 
    boost::scoped_ptr<int> ptr; 
    public: 
    MyClass() : ptr(new int) { *ptr = 0; std::cout<<"MyClass Allocated\n"; } 
    ~MyClass() { std::cout<<"MyClass De-allocated\n"; } 
    int increment() { return ++*ptr; } 
}; 

void doSomething()
{
    boost::scoped_ptr<MyClass> myinst(new MyClass); 
    throw 3; 
} 

int main() 
{
    try 
    {
        doSomething();    
    }
    catch(int &obj)
    {
        std::cout<<"Exception Handled";
    }

} 

Ref1C++03 15.5.1 The terminate() function

In the following situations exception handling must be abandoned for less subtle error handling techniques:
....
— when the exception handling mechanism cannot find a handler for a thrown exception (15.3),
....

In such cases,

  1. void terminate();

is called (18.6.3). In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before terminate() is called. In all other situations, the stack shall not be unwound before terminate() is called. An implementation is not permitted to finish stack unwinding prematurely based on a determination that the unwind process will eventually cause a call to terminate().

Pennsylvania answered 2/4, 2012 at 6:58 Comment(2)
But that does not happen. I put a `throw in the main() and the program aborted without calling the destructor.Hbeam
@cppcoder: It should.Unless your MyClass destructor throwed another exception.Can you post the code example that shows you this behavior?Pennsylvania
A
7

Because calling the destructor doesn't make sense in this case.

You only destruct things which are constructed, yet your object never fully constructs. Your class members have been constructed, though, and will have their destructors called.

Alwyn answered 2/4, 2012 at 6:36 Comment(6)
If any exception occurs in the program, scoped_ptr will ensure that the memory allocated is destroyed properly. So in which case ~MyClass will be invoked?Hbeam
@cppcoder: I'm not sure I understand your question. Because ptr is fully initialized within the constructor, it is destructed (and its memory freed) when you throw an exception. It's also destructed when MyClass destructs, but that's not the case here.Alwyn
My question is about this line. boost::scoped_ptr<MyClass> myinst(new MyClass); The scoped_ptr class template stores a pointer to a dynamically allocated object. So, shouln't it be released?Hbeam
@cppcoder: It has nothing to release, since it never held any object. But yes, it would release whatever it held if it had something.Alwyn
What if the exception happened in the main() instead of constructor?Hbeam
@cppcoder: Then the destructor of all local objects would be called, and myinst would free whatever resources it holds.Alwyn
S
1

If a constructor throws exception, then the destructor of the class will not be called, because the object is not fully constructed.

See this link how to manage resources in such situation:

http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.10

Stylistic answered 2/4, 2012 at 6:44 Comment(0)
N
0

When the exception is thrown from the constructor (beginning or half way or at the end of the call), then it's assured that the object is not constructed.
So it's well defined not to invoke the destructor of an object which was never constructed.

Here is one related FAQ from Bjarne's website.

Nutcracker answered 2/4, 2012 at 6:37 Comment(0)
M
0

The destructor for MyClass was never invoked because no objects of type MyClass were ever constructed. Each attempt to construct one was aborted, due to the exception being thrown.

As an aside, if you want your debug messages to display -- especially if you're dealing with the program crashing -- you really ought to flush the streams: i.e. using std::endl instead of '\n' at the end of line. (or inserting std::flush)

While merely using '\n' often works, there are enough situations where it fails and it's really, really confusing to debug if you don't make a habit of doing things right.

Milburr answered 2/4, 2012 at 6:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.