谁删除了在构造函数中有异常的"新"操作期间分配的内存?

Joh*_*ohn 49 c++ constructor memory-leaks exception

我真的不敢相信我找不到明确的答案......

在使用new运算符初始化C++类构造函数抛出异常后,如何释放分配的内存.例如:

class Blah
{
public:
  Blah()
  {
    throw "oops";
  }
};

void main()
{
  Blah* b = NULL;
  try
  {
    b = new Blah();
  }
  catch (...)
  {
    // What now?
  }
}
Run Code Online (Sandbox Code Playgroud)

当我尝试这个时,b在catch块中是NULL(这是有道理的).

在调试时,我注意到conrol在它到达构造函数之前进入了内存分配例程.

这在MSDN网站上似乎证实了这一点:

当new用于为C++类对象分配内存时,在分配内存后调用对象的构造函数.

因此,请记住,b永远不会分配局部变量(即在catch块中为NULL),如何删除分配的内存?

得到一个跨平台的答案也很好.即,C++规范说什么?

澄清:我不是在讨论类在c'tor中分配内存然后抛出的情况.我很欣赏在这种情况下,不会召唤人.我在谈论用于分配THE对象的内存(Blah在我的例子中).

Kos*_*801 39

你应该在这里这里参考类似的问题.基本上,如果构造函数抛出异常,则可以安全地再次释放对象本身的内存.虽然,如果在构造函数中声明了其他内存,那么在离开带有异常的构造函数之前,您可以自行释放它.

对于您的问题,WHO删除内存,答案是new-operator背后的代码(由编译器生成).如果它识别出离开构造函数的异常,它必须调用类成员的所有析构函数(因为那些已经在调用构造函数代码之前已成功构造)并释放它们的内存(可以与析构函数调用一起递归完成,很可能是通过调用它们的正确删除)以及释放为此类本身分配的内存.然后它必须将构造函数中捕获的异常重新抛出到new的调用者.当然可能还有更多的工作需要完成,但我无法从头脑中提取所有细节,因为它们取决于每个编译器的实现.

  • 您自己的运算符new和delete的实现只需要知道如何分配和释放内存.额外的工作由编译器完成.当你说'new Blah()`时,编译器生成代码以(1)调用new运算符(只分配内存),(2)调用c'tor,以及(3)如果出现问题则调用delete运算符. (4认同)
  • @Adrian:只是迂腐;-) 如果 (1) 完成并且 (2) 抛出,则调用 (3)。 (3认同)

cop*_*pro 26

如果一个对象因为构造函数抛出异常而无法完成销毁,那么首先发生的事情(这是构造函数的特殊处理的一部分)是所有已构造的成员变量都被销毁 - 如果在初始化列表中抛出异常,这意味着只有初始化程序已完成的元素才会被销毁.

然后,如果正在分配对象,则使用传递给的相同附加参数调用new适当的释放函数(operator delete)operator new.例如,new (std::nothrow) SomethingThatThrows()将分配内存operator new (size_of_ob, nothrow),尝试构造SomethingThatThrows,销毁任何成功构造的成员,然后operator delete (ptr_to_obj, nothrow)在传播异常时调用- 它不会泄漏内存.

你必须要小心的是连续分配几个对象 - 如果其中一个对象抛出,之前的对象将不会被自动释放.解决这个问题的最好方法是使用智能指针,因为作为本地对象,它们的析构函数将在堆栈展开期间被调用,并且它们的析构函数将正确释放内存.


Mar*_*ork 6

如果构造函数抛出为该对象分配的内存,则会自动将其返回给系统.

请注意,不会调用抛出的类的析构函数.
但是也会调用任何基类的析构函数(基础构造函数已经完成).

注意:
正如大多数其他人都注意到的,会员可能需要一些清理.

已完全初始化的成员将调用其析构函数,但如果您拥有任何RAW指针成员(即在析构函数中删除),则必须在执行抛出之前进行一些清理(不使用拥有的另一个原因)你班上的RAW指针).

#include <iostream>

class Base
{
    public:
        Base()  {std::cout << "Create  Base\n";}
        ~Base() {std::cout << "Destroy Base\n";}
};

class Deriv: public Base
{
    public:
        Deriv(int x)    {std::cout << "Create  Deriv\n";if (x > 0) throw int(x);}
        ~Deriv()        {std::cout << "Destroy Deriv\n";}
};

int main()
{
    try
    {
        {
            Deriv       d0(0);  // All constructors/Destructors called.
        }
        {
            Deriv       d1(1);  // Base constructor and destructor called.
                                // Derived constructor called (not destructor)
        }
    }
    catch(...)
    {
        throw;
        // Also note here.
        // If an exception escapes main it is implementation defined
        // whether the stack is unwound. By catching in main() you force
        // the stack to unwind to this point. If you can't handle re-throw
        // so the system exception handling can provide the appropriate
        // error handling (such as user messages).
    }
}
Run Code Online (Sandbox Code Playgroud)


Mic*_*urr 6

从C++ 2003标准5.3.4/17 - 新增:

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

所以可能有或没有泄漏 - 这取决于是否可以找到适当的解除分配器(通常情况下,除非操作符new/delete被覆盖).如果有合适的解除分配器,编译器负责如果构造函数抛出则调用它进行连接.

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