从构造函数抛出异常时,为什么会出现内存泄漏?

时间:2015-06-13 17:40:52

标签: c++ c++11 exception memory-leaks

我读了 Paul Deitel 这本书 C ++如何编程第8版。第p455页有一个声明:

  

当从新构造函数中创建的对象的构造函数抛出异常时,将释放该对象的动态分配内存。

为了验证这个陈述,我写了如下代码:

#include <iostream>
#include <exception>
#include <memory>

class A{
public:
  A(){std::cout << "A is coming." << std::endl;}
  ~A(){std::cout << "A is leaving." << std::endl;}
};
class B
{
public:
  B()
  {
    std::cout << "B is coming." << std::endl;
    A b;
    throw 3;
  }
  ~B(){std::cout << "B is leaving." << std::endl;}
};

int main(void)
{
    try
    {
        std::shared_ptr<B> pi(new B);
    }
    catch(...)
    {
      std::cout << "Exception handled!" << std::endl;
    }
}

输出结果为:

B is coming.
A is coming.
A is leaving.
Exception handled!

这表明B的析构函数没有被调用,这似乎与上面的陈述相冲突。

我的代码是否正确以验证声明?如果没有,我应该如何修改它?如果是,那是否意味着该陈述是错误的?

2 个答案:

答案 0 :(得分:4)

你混淆了两件事:

  • 记忆被释放
  • 被称为析构函数

你已经证明后者并没有发生,这是有道理的:你怎么能摧毁那些没有正确建造的东西?注意,成员变量调用它们的析构函数,因为在构造函数抛出异常时,所有成员变量都已完全构造。

但这与发布的内存无关, 肯定会发生。

  

[C++11: 15.2/2]:任何存储持续时间的对象,其初始化或销毁由异常终止,将为其所有完全构造的子对象(不包括类似联合的类的变体成员)执行析构函数,即对于主要构造函数(12.6.2)已完成执行且析构函数尚未开始执行的子对象。类似地,如果对象的非委托构造函数已完成执行,并且该对象的委托构造函数以异常退出,则将调用该对象的析构函数。如果对象是在new-expression中分配的,则调用匹配的释放函数(3.7.4.2,5.3.4,12.5)(如果有的话)以释放对象占用的存储空间。

答案 1 :(得分:2)

这意味着B的ctor中的所有内容都是异常点。 B本身的实例从未构建过,因此不得破坏它。另请注意,pi从未构建过。

std::shared_ptr<B> pi(new B)  - start with new B
new B                         - triggers the ctor of B
std::cout ...                 - the output 
A b;                          - construct an A
throw 3;                      - calls ~A()
                              - rewind, new B is "aborted"
                              - std::shared_ptr<B> pi(new B) is "aborted"

你可以修改你的代码,看看std::shared_ptr的构造函数永远不会被你的新类替换掉,只需要一个指针:

struct T {
  T(B*) { std::cout << "T::T()\n"; }
};
...
try
{
    T pi(new B);  // instead of std::shared_ptr<B> pi(new B);
}
...

T的构造函数不会被命中(参见“pi从未构建过”)。

现在假设B的构造函数将按以下方式分配内存:

B()
{
  A* a = new A();   // in contrast to A a;
  throw 3;
}

以前是A::~A()被调用,这是一个解构,我们现在有一个指针,指针不需要解构。但是,分配和分配给a的内存已删除。 (如果您使用了智能指针std::unique_ptr<A> a = std::make_unique<A>();,则内存将被释放,因为std::unique_ptr<A>的析构函数被调用并且它将释放内存。)