在构造函数中抛出异常时,不会调用析构函数

cpp*_*der 8 c++ boost scoped-ptr

为什么在此代码中不调用析构函数?

#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';
}
Run Code Online (Sandbox Code Playgroud)

编辑

从答案中,了解当构造函数中发生异常时,不会调用析构函数.但是如果异常发生在main()中,即在完全实例化MyClass对象之后,是否会调用MyClass析构函数?如果没有,那为什么它是一个智能指针?

添加代码

#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';
}
Run Code Online (Sandbox Code Playgroud)

输出:

MyClass Allocated
terminate called after throwing an instance of 'int'
Aborted
Run Code Online (Sandbox Code Playgroud)

Alo*_*ave 20

C++对象的生命周期仅在其构造函数成功完成后才开始.
由于在构造函数调用完成之前抛出了异常,因此没有完整的对象,因此没有析构函数.

Herb Sutter 很好地解释了这一点,引用他的话:

问: 从构造函数中发出异常是什么意思?

答:这意味着建筑已经失败,对象从未存在过,它的生命从未开始.实际上,报告构造失败的唯一方法 - 即无法正确构建给定类型的功能对象 - 是抛出异常.(是的,现在有一个过时的编程约定,"如果你遇到麻烦,只需将状态标志设置为'坏'并让调用者通过IsOK()函数检查它."我现在将对此进行评论.)

从生物学角度来说,
概念发生了 - 构造者开始了 - 但尽管做了最大的努力,然后是流产 - 构造函数从未跑到任期(ination).

顺便说一句,这就是为什么如果构造函数没有成功就永远不会调用析构函数 - 没有什么可以破坏的."It cannot die, for it never lived."请注意,这使得这句话"an object whose constructor threw an exception"真的变成了矛盾.这样的事情甚至比一个前对象还要少......它从来没有生存过,从来没有,从未放过第一个.这是一个非对象.

我们可以总结一下C++构造函数模型如下:

或者:

(a)构造函数通常在到达其结尾或返回语句时返回,并且该对象存在.

要么:

(b)构造函数通过发出异常退出,并且该对象不仅现在不存在,而且从不作为对象存在.

编辑1:
但是如果异常发生main(),即在MyClass对象完全实例化之后,是否MyClass会调用析构函数?

是的,它会!
这就是使用的目的scoped_ptr,一旦抛出异常main,Stack Unwinding将导致所有本地对象被释放,这意味着myinst(它驻留在堆栈上)也将被释放,这反过来将调用析构函数MyClass.

如有疑问,请参阅Boost文档:

scoped_ptr类模板存储一个指向一个动态分配的对象.(动态分配的对象是使用C++新表达式分配的.)指向的对象保证在删除时被删除scoped_ptr,或者通过显式删除reset

编辑2:
为什么你编辑的程序会崩溃?
您的程序显示崩溃,因为,您抛出异常,但您从未捕获它.当这样的场景发生时,调用一个特殊的函数调用terminate(),其默认行为是调用.abort()它是实现定义的行为,terminate()在此特定场景中调用之前是否堆栈是Unwound 参考文献1.查看你的实现没有&你不应该依赖于此行为也是如此.

您可以按如下方式修改程序以处理异常,您应该得到您期望的行为:

#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";
    }

} 
Run Code Online (Sandbox Code Playgroud)

Ref 1 C++ 03 15.5.1 terminate()函数

在下列情况下,必须放弃异常处理以获得不那么微妙的错误处理技术:
....
- 当异常处理机制找不到抛出异常的处理程序时(15.3),
....

在这种情况下,

  1. void terminate();

被称为(18.6.3).在没有找到匹配处理程序的情况下,无论堆栈是否在terminate()被调用之前被展开,它都是实现定义的.在所有其他情况下,堆栈在terminate()被调用之前不应解开.基于确定展开过程最终将导致调用,不允许实现过早地完成堆栈展开terminate().


GMa*_*ckG 7

因为在这种情况下调用析构函数没有意义.

你只破坏构造的东西,但你的对象永远不会完全构造.但是,你的班级成员已经建成了,他们将会召唤他们的析构函数.