Exception in derived class constructor
Im having some problems to handle constructor exception in derived classes. When the derived class constructo开发者_开发知识库r throws an error, but the parent class has allocated some objects. Will the parent class destructor be called?
Example:
class A
{
A() { /* Allocate some stuff */ };
virtual ~A() { /* Deallocate stuff */ };
};
class B : public A
{
B()
{
/* Do some allocations */
...
/* Something bad happened here */
if (somethingBadHappened)
{
/* Deallocate B Stuff */
...
/* Throws the error */
throw Error; /* Will A destructor be called? I know B destructor won't */
};
};
~B() { /* Deallocate B Stuff */ };
}
And i was wondering if it is a good idea to do the following:
B()
{
/* Do some allocations */
...
/* Something bad happened here */
if (somethingBadHappened)
{
/* Deallocate B Stuff */
this->~B();
/* Throws the error */
throw Error; /* Will A destructor be called? I know B destructor won't */
};
};
If not, whats is a decent way to do such things?
An exception will cause the stack to unwind to a point where the exception is properly caught. This means that any objects created in the scope prior to where the exception is thrown will be destructed, including base class objects as in this example.
Try this:
#include <iostream>
class A
{
public:
A() { std::cout << "A::A()\n";}
~A() {std::cout << "A::~A()\n";}
};
class B : public A
{
public:
B()
{
std::cout << "B::B()\n";
throw 'c';
}
// note: a popular point of confusion --
// in this example, this object's destructor
// WILL NOT BE CALLED!
~B()
{
std::cout << "B::~B()\n";
}
};
int main()
{
try
{
B b;
}
catch(...)
{
std::cout << "Fin\n";
}
return 0;
}
Output should be: (note B::~B()
is not called)
A::A()
B::B()
A::~A()
Fin
Calling the destructor manually as you've shown in your question will be safe as long as you don't try to free resources that you've not yet allocated. Better to wrap those resources in some type of RAII
container (std::auto_ptr
, boost::shared_ptr
, etc.)to avoid the necessity of calling the destructor.
Mooing Duck has provided a very nice illustration of how the stack unwinding works when an exception is thrown in a constructor:
Your abortive attempt to write a clean constructor B::B()
in the second part of the question highlights the awkwardness of a design that takes too much responsibility in one class. If you use only single-responsibility components, you can often get away with not writing any explicit error checks at all and let the exception handling mechanism do its work, recursively.
Consider this:
B::B()
{
try { this->p1 = get_dangerous_pointer(); }
catch(...) { throw; } // OK
try { this->p2 = suicidal_function(); }
catch(...) {
clean_up(p1);
throw;
}
try { this->p3 = get_monstrous_amounts_of_memory(); }
catch(...)
{
clean_up(p2);
clean_up(p1);
throw;
}
}
As you can see, writing a correct constructor for a class that has even just three different responsibilities is a maintenance nightmare.
The correct solution is to make each resource owned by a wrapper class whose only responsibility is to own that resource, and clean-up happens automagically even in the face of the most exceptional exception.
Also note that you have to be extremely careful when calling member functions from within any constructor. An object's lifetime doesn't begin until after a constructor has finished, so while you're in the constructor you're working with an "object under construction" - a bit like open-heart surgery... on yourself. In particular, you mustn't call the destructor, because you are only allowed to destroy complete objects.
The best idea is to catch exceptions within the construct and then put the object into a state where things will produce the errors (e.g. object to read file, opening file in constructor fails, then the read will not work).
Just keep the object consistent.
Haven't thought this all the way through but maybe consider creating the object in a try/catch block. If the constructor throws an exception, delete
the object if it was created using new
.
try
{
B* b = new B();
}
catch
{
delete b;
//log error
}
If you do not use new
to allocate memory for b
, you do not need to call delete in the catch
block.
Make sure that your B
destructor doesn't call delete
on objects that were never created. I would recommend setting all members that are pointers to objects equal to 0 in your constructor before doing anything that could cause an exception. That way, if the destructor is called, delete
ing them is safe.
精彩评论