我正在学习C ++中的RAII习语,以及如何使用智能指针。
在我的阅读中,我遇到了两件对我而言似乎相互矛盾的事情。
引自http://www.hackcraft.net/raii/:
...如果已经创建了具有RAII语义的成员对象,并且在构造函数完成之前发生了异常,那么它的析构函数将作为堆栈展开的一部分被调用。因此,即使没有使用成员RAII对象完全构造,控制多个资源的对象也可以保护它们的清理。
但引自http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.10:
如果构造函数抛出异常,则不会运行该对象的析构函数。如果您的对象已经完成了需要撤消的操作(例如分配一些内存,打开文件或锁定信号量),则必须通过对象内的数据成员记住这些“需要撤消的内容”。
然后第二个链接源建议使用智能指针来处理已在构造函数中分配的事物的问题。
那么在这些场景中实际发生了什么?
答案 0 :(得分:15)
你误解了第一句话。这并不难,因为它令人困惑。
如果已经创建了具有RAII语义的成员对象并且在构造函数完成之前发生了异常,那么它的析构函数将作为堆栈展开的一部分被调用。
这就是它所说的。这就是的含义:
如果已经创建了具有RAII语义的成员对象,并且在外部对象的构造函数完成之前发生了外部对象中的异常,那么成员对象的< / strong>析构函数将作为堆栈展开的一部分进行调用。
看到区别?这个想法是成员对象完成了它的构造函数,但拥有的类型却没有。它在构造函数中抛出某个地方(或者在该构造函数之后初始化的另一个成员的构造函数)。这将导致所有成员的析构函数被调用(所有成员完成构造,即),但不它自己的析构函数。
以下是一个例子:
class SomeType
{
InnerType val;
public:
SomeType() : val(...)
{
throw Exception;
}
};
创建SomeType
实例时,它会调用InnerType::InnerType
。只要不抛出它,它就会进入SomeType
的构造函数。当它抛出时,它将导致val
被销毁,从而调用InnerType::~InnerType
。
答案 1 :(得分:5)
这里没有矛盾;在不同的语境中使用了一些令人困惑的术语。
如果对象的构造函数抛出异常,则会发生以下情况(假设捕获到异常):
因此,任何由智能指针或其他RAII对象管理的资源都是被破坏对象的数据成员,这些资源确实会被清除,但是在对象的析构函数中进行清理的专用代码将不会触发
希望这有帮助!
答案 2 :(得分:2)
这两个陈述并不矛盾,但第一个陈述有一些不幸的语言。当某个对象的构造抛出时,它的解构函数将不会被调用,但该对象拥有的所有对象都将被其各自的解构器破坏。
因此,使用RAII和智能指针,对象的任何指针成员的析构函数将独立于到期对象的析构函数进行调用。原始指针不释放它们指向的内存,必须手动删除。是否应该释放拥有对象的构造函数抛出原始指针。智能指针不会发生这种情况。