【问题标题】:Does the memory get released when I throw an exception?当我抛出异常时,内存是否会被释放?
【发布时间】:2010-11-21 07:44:47
【问题描述】:

我正在与一些同事讨论当您在动态分配的类中抛出异常时会发生什么。我知道malloc 被调用,然后是类的构造函数。构造函数永远不会返回,那么malloc 会发生什么?

考虑以下示例:

class B
{
public:
    B()
    {
        cout << "B::B()" << endl;
        throw "B::exception";
    }

    ~B()
    {
        cout << "B::~B()" << endl;          
    }
};

void main()
{
    B *o = 0;
    try
    {
        o = new B;
    }
    catch(const char *)
    {
        cout << "ouch!" << endl;
    }
}

分配的内存 o 会发生什么情况,它会泄漏吗? CRT 是否捕获构造函数的异常并释放内存?

干杯!
丰富

【问题讨论】:

  • 简短回答:是的。有关详细信息,请参阅 Cătălin Pitiş 答案。

标签: c++ exception constructor handler


【解决方案1】:

构造函数抛出异常时,释放new分配的内存,但不调用B类的析构函数。

【讨论】:

  • 是的。只是为了补充您的答案...不调用析构函数有充分的理由:从未创建过该对象。只有在特定时间段内存在的对象才会调用析构函数。那些是构造函数成功完成的情况。
  • 是的,你是对的。只有在构造函数成功执行后,才认为对象已创建(因此可破坏)。
  • 注意:任何完全构造的成员和基类的析构函数都会被调用。
【解决方案2】:

来电

new B();

解决两件事:

  • 使用操作符 new() 进行分配(全局操作符或类特定操作符,可能是使用语法 new (xxx) B() 的放置操作符)
  • 调用构造函数。

如果构造函数抛出,则调用相应的运算符 delete。对应的删除是放置的情况是唯一调用放置删除运算符而不使用语法 ::operator delete() 的情况。 delete x;delete[] x; 不要调用placement delete 运算符,并且没有与placement new 类似的语法来调用它们。

注意,虽然不会调用 B 的析构函数,但已经构造的子对象(成员或 B 和 B 的基类)将在调用 operator delete 之前被销毁。未调用的构造函数是 B 的构造函数。

【讨论】:

  • 重要附录:B 的构造函数没有运行,从来没有那个类型的对象。如果在触发异常之前在 B 的构造函数中调用了 new(),则它 not 被释放 - 并且您不再拥有指向该内存的指针,因此您无法 delete()它。 KEEP AWAY from new() 在构造函数中。
  • 如果您使用 RAII 技术(例如,使用 std::auto_ptr),构造函数中的 new() 没什么大不了的。
  • @DevSolar:一切都取决于 new 的结果存储在哪里。如果它在一个智能指针中,以太本地的构造函数或类 B 或其父类的成员,则该对象将被销毁,并且在 B 的构造函数中但在抛出异常之前分配的内存将被释放。显然,如果您执行 new Foo(); throw "bar",内存不会被释放,Foo 不会被销毁。在构造函数或其他地方。我会澄清 B 的析构函数没有被调用。
  • 如果构造函数抛出析构函数不是(我重复NOT)调用。只有完全构造的成员和任何完全构造的基类才会调用其构造函数。
  • @DevSolar:内存是自动释放的(注意“新语句”[调用构造函数]永远不会完成)。如果您使用的是智能指针,它将无济于事,因为永远不会输入智能指针的构造函数,因此对释放没有帮助。
【解决方案3】:

在这种情况下,您的对象 o 实际上并没有被构造,并且由 new 分配的内存被释放。因此,析构函数不会被调用。所以你不需要打电话:

delete o;

一个有趣的设计模式是 RAII——资源获取即初始化。在该模式中,您使用构造函数来封装资源的获取,并在析构函数中释放资源。如果无法获取资源,则在构造函数中抛出 - 就像您的示例一样。因此,如果你有一个有效的对象,你就有了资源。

如果对象被构造,那么你已经成功获取了资源。这意味着在对象的生命周期内,您拥有该资源。当对象被删除时,资源被释放。如果对象从未被构造,那么您就永远不会获得资源。参见维基百科:

http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization

【讨论】:

  • 因此,如果构造函数在触发异常之前已经完成了一些资源获取工作,那么构造函数的工作就是在引发异常之前进行清理。
【解决方案4】:

来自 C++ 2003 标准 5.3.4/17 - 新:

如果上述对象初始化的任何部分因抛出异常而终止并且可以找到合适的释放函数,则调用释放函数以释放构造对象的内存,之后异常继续传播在新表达式的上下文中。如果找不到明确匹配的释放函数,则传播异常不会导致对象的内存被释放。 [注意:这适用于被调用的分配函数不分配内存的情况;否则,很可能导致内存泄漏。 ]

因此可能存在泄漏,也可能不存在泄漏 - 这取决于是否可以找到合适的解除分配器(通常情况下,除非操作员 new/delete 已被覆盖)。如果有合适的解除分配器,则如果构造函数抛出,编译器负责连接调用它。

请注意,这或多或少与构造函数中获取的资源发生的情况无关,这是我第一次尝试回答时讨论的问题 - 并且是许多常见问题解答、文章和帖子中讨论的问题。

【讨论】:

猜你喜欢
  • 2012-01-04
  • 2018-12-31
  • 2013-06-18
  • 2019-05-15
  • 2016-06-19
  • 1970-01-01
  • 2013-07-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多