8

为什么这段代码没有调用析构函数?

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

编辑

从答案中可以看出,当构造函数发生异常时,不会调用析构函数。但是如果异常发生在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';
}

输出:

MyClass Allocated
terminate called after throwing an instance of 'int'
Aborted
4

5 回答 5

20

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

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

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

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

用生物学的术语来说,
受孕发生了——构造函数开始了——但尽管尽了最大的努力,但还是流产了——构造函数从未跑到term(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++ 的 new 表达式进行分配。)指向的对象保证被删除,无论是在销毁时scoped_ptr,还是通过显式的reset

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

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

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

} 

参考1 C++03 15.5.1 终止()函数

在以下情况下,必须放弃异常处理以使用不太微妙的错误处理技术:
....
- 当异常处理机制无法找到抛出异常的处理程序时 (15.3),
...

在这种情况下,

  1. 无效终止();

称为(18.6.3)。在没有找到匹配处理程序的情况下,在调用之前堆栈是否展开是实现定义的terminate()。在所有其他情况下,堆栈在调用之前不得展开terminate()。基于展开过程最终将导致调用terminate().

于 2012-04-02T06:58:15.827 回答
7

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

您只会破坏已构造的事物,而您的对象永远不会完全构造。但是,您的类成员已经被构造,并且将调用它们的析构函数。

于 2012-04-02T06:36:41.487 回答
1

如果构造函数抛出异常,那么类的析构函数将不会被调用,因为对象没有完全构造。

请参阅此链接如何在这种情况下管理资源:

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

于 2012-04-02T06:44:57.857 回答
0

当构造函数抛出异常时(开始或中途或调用结束时),则可以确保对象没有被构造。
因此,不调用从未构造的对象的析构函数是很好的定义。

这是Bjarne 网站上的一个相关常见问题解答。

于 2012-04-02T06:37:21.183 回答
0

MyClass因为从未构造过任何类型的对象,所以从未调用过析构函数 for MyClass。由于抛出异常,每次构建一个的尝试都被中止。

顺便说一句,如果您希望显示调试消息——尤其是在处理程序崩溃时——您真的应该刷新流:即在行尾使用std::endl而不是。'\n'(或插入std::flush

虽然仅使用'\n'经常有效,但有足够多的情况下它会失败,如果您没有养成正确做事的习惯,调试起来真的非常令人困惑

于 2012-04-02T06:44:01.153 回答