我在派生类中处理构造函数异常时遇到了一些问题。当派生类构造函数抛出错误时,父类已经分配了一些对象。是否会调用父类析构函数?
示例:
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 */ };
}
我想知道做以下事情是否是一个好主意:
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 */
};
};
如果没有,什么是做这种事情的好方法?
答案 0 :(得分:6)
异常将导致堆栈展开到正确捕获异常的位置。这意味着在抛出异常之前在范围内创建的任何对象都将被破坏,包括本例中的基类对象。
试试这个:
#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;
}
输出应为:(注意B::~B()
未被调用)
A::A()
B::B()
A::~A()
Fin
只要您不尝试释放尚未分配的资源,就会在问题中显示手动调用析构函数。最好将这些资源包装在某种类型的RAII
容器(std::auto_ptr
,boost::shared_ptr
等)中,以避免调用析构函数的必要性。
Mooing Duck提供了一个非常好的例子,说明了在构造函数中抛出异常时堆栈展开的工作方式:
答案 1 :(得分:0)
你在问题的第二部分中写一个干净的构造函数B::B()
的失败尝试突出了一个设计的尴尬,它在一个类中承担了太多的责任。如果您只使用单一责任组件,那么您通常可以完全不编写任何显式错误检查,并让异常处理机制以递归方式完成其工作。
考虑一下:
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;
}
}
正如您所看到的,为一个甚至只有三个不同职责的类编写一个正确的构造函数是一个维护噩梦。
正确的解决方案是让每个资源都由一个包装类所拥有,它的唯一责任是拥有该资源,即使面对最特殊的异常,清理也会自动发生。
另请注意,在任何构造函数中调用成员函数时,您必须非常小心。一个对象的生命周期直到构造函数完成后才开始,所以当你在构造函数中时,你正在处理一个“正在构建的对象” - 有点像开心手术......对你自己。特别是,您不能调用析构函数,因为您只能销毁完整的对象。
答案 2 :(得分:0)
最好的想法是捕获构造中的异常,然后将对象置于事物将产生错误的状态(例如,读取文件的对象,构造函数中的打开文件失败,然后读取将不起作用)。
保持对象一致。
答案 3 :(得分:-1)
没有想到这一切,但可以考虑在try / catch块中创建对象。如果构造函数抛出异常,delete
该对象(如果它是使用new
创建的。
try
{
B* b = new B();
}
catch
{
delete b;
//log error
}
如果您不使用new
为b
分配内存,则无需在catch
块中调用delete。
确保您的B
析构函数不会对从未创建过的对象调用delete
。我建议在做任何可能导致异常的事情之前,在构造函数中设置指向对象指针的所有成员。这样,如果调用析构函数,delete
它们就是安全的。