【问题标题】:At what moment is an object exactly "created"?一个对象是在什么时候被“创建”的?
【发布时间】:2012-08-18 17:21:55
【问题描述】:

假设我们有一个带有构造函数的类:

class MyClass
{
public:
    MyClass()
    {
    //our code goes here
    throw "OMG";//well, some code that throws an exception
    }
};

现在,当发生异常时,正在执行堆栈展开。我也知道,如果从构造函数中抛出异常,相应对象的析构函数将不会被调用,因为对象从一开始就没有完全“创建”。

我对此有点困惑。对我来说,这意味着该对象仅在构造函数完成时才被视为“已创建”。但显然,所有内存都分配在构造函数调用之前(或之后)的某个位置,因为我们可以在构造函数中操作对象的成员。

那么究竟什么时候在内存中创建了对象,导致异常的对象的内存发生了什么?

【问题讨论】:

    标签: c++ memory exception-handling constructor


    【解决方案1】:

    对象的内存被释放,但不调用析构函数(例如,如果你没有正确处理构造函数中的异常,在构造函数中创建的动态分配的指针不会被释放)。在 C++11 中,规范说当且仅当 one 构造函数完全完成时调用析构函数(相关因为构造函数可以引用另一个构造函数)。

    示例(new int()分配的内存没有被释放):

    struct Broken {
       int * i;
       Broken () {
          i = new int {5};
          OperationCausingException ();
       }
    
       ~Broken {
          delete i;
       }
    }
    
    try {
       Broken * b = new Broken {};
    } catch (...) {
    }
    // memory leak: *b is freed, but b->i still exists
    

    【讨论】:

    • 这就是为什么你不应该在抛出构造函数中分配内存——或者使用异常安全的智能指针。
    【解决方案2】:

    内存分配对象构造在C++中是两个不同的东西。编译器只是确保当它们被一起调用时(这是常规 - 即非放置 - new 运算符的情况)并且构造失败,分配被恢复。

    此外,当类具有子对象(基类和/或字段)时,编译器会以固定顺序调用它们的构造函数,并确保在其中一个抛出时,已经构造的子对象被处理掉正确地(即,调用它们的析构函数)。

    【讨论】:

      【解决方案3】:

      内存在构造函数体之前分配。

      如果构造函数失败,自动分配的内存被释放

      而“自动分配”的重音很重要——如果你在构造函数中动态分配了内存,而构造函数失败了(例如,你可能在throw "OMG"之前使用过new),这块内存将泄漏。

      那是因为——你已经分配了这块内存,你需要释放它。

      你是对的,没有调用析构函数,但析构函数不是释放内存的析构函数,它分配给类的 auto 成员。
      它(基本上)用于释放内存,由用户分配(在构造函数中或其他地方)。


      换句话说,为一个对象分配内存与该对象的构造是不同的。

      另一个例子 - 如果你动态创建一个对象,比如:

      MyObj* = new MyObj;
      

      这将:

      • 致电operator new
      • 然后调用MyObj的构造函数

      看,两者都不一样。

      【讨论】:

      • 它是之前,但不是“正确”之前。在构造函数体之前,初始化列表被处理并且一些成员可能被初始化。
      • 不仅是初始化列表。如果类包含非 POD 成员,即使它们不在初始化列表中,它们也会被初始化。
      猜你喜欢
      • 1970-01-01
      • 2017-11-15
      • 2019-04-21
      • 1970-01-01
      • 2017-09-23
      • 2011-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多