【问题标题】:Is object created without new operator deleted in a specific case in C++在 C++ 中的特定情况下,是否在没有删除 new 运算符的情况下创建对象
【发布时间】:2011-11-08 21:55:48
【问题描述】:

如果我们有如下代码sn-p:

MyObject my_object = MyObject(0);
my_object = MyObject(1);

MyObject(0) 会发生什么?它被删除了吗?看看我读到的关于它的内容,只有在我们离开创建范围时才应该删除它,所以答案可能是否定的。如果是这种情况,除了使用指针之外,还有什么方法可以显式删除它?

【问题讨论】:

    标签: c++ object new-operator


    【解决方案1】:
    MyObject my_object = MyObject(0);
    

    这一行使用MyObject 的构造函数在堆栈上创建my_object,该构造函数可以接受int

    my_object = MyObject(1);
    

    这一行再次使用与第一行相同的构造函数创建了一个临时 MyObject。然后通过调用赋值运算符将其分配给my_object。如果您没有提供此运算符,那么编译器将为您创建一个执行 shallow 副本的运算符。当此语句完成时,临时 MyObject 将超出范围并调用它的析构函数。

    当您的my_object 超出范围时,它又会以同样的方式被销毁。您在任何时候都不需要手动删除它,因为所有内容都在堆栈上分配。

    【讨论】:

    • 所以只是为了检查我是否理解正确,我们正在创建 MyObject 类的 suma sumarum 3 个实例,其中两个将在第二行执行后被删除(最后,不是直接导致由)?
    • @nivwus 实际上,在您提供的示例中,应该只创建 2 个对象。这是因为编译器足够聪明,可以看到 my_object 正在创建,因此它可以避免创建额外的临时对象。基本上,第一行与您所做的相同:MyObject my_object(0);
    • 我不同意,我认为它高度依赖于编译器。至少在没有优化的情况下,会创建和销毁 3 个对象。
    • @Luch 虽然理论上可以创建 3 个对象,但实际上并非如此。我已经在包括 msvc2010、c++ builder2007、gcc-4.5.2、digital mars 编译器和 Clang 在内的很多编译器上对此进行了测试,并且在所有情况下都不会调用复制构造函数——始终避免中间步骤。只是为了好玩,我也在 C++Builder6 上测试了这个(一个有将近 10 年历史的工具链),也没有创建额外的第三个对象。是的,在所有情况下都启用了调试并禁用了优化。
    • 无论如何,我认为这不值得投反对票。我的回答中提供的材料是正确的,就 OP 而言,他使用的任何编译器都会这样做。你很难找到任何没有的 c++ 编译器。
    【解决方案2】:

    在谈论新创建的对象时,内存中有两个主要区域:堆栈和堆。堆包含使用 new 动态创建的所有对象。这些对象需要使用 delete 运算符显式删除。堆栈是特定于范围的,堆栈上定义的所有对象都将被自动删除。由于您不使用 new,因此您的所有对象都将在其作用域结束时被销毁。

    假设没有编译器优化,您的代码大致转换为:

    {
       MyObject my_object;
       MyObject tempObject0(0);
       my_Object = tempObject0;
       MyObject tempObject1(1);
       my_Object = tempObject;
    }//3 objects are deleted by this point (in theory)
    

    还要注意两者的区别

    MyObject myObject(0);
    

    MyObject myObject = MyObject(0);
    

    第二种情况会创建一个临时对象,因此效率会降低。当然,这一切都假设没有优化。根据编译器的不同,它可能会转换为相同的东西。

    【讨论】:

    • // 3 Objects are deleted here 是错误的。临时对象在包含它们的完整表达式的末尾被销毁,而不是在包含块的出口处。因此,MyObject(0)MyObject(1) 被创建之前就被销毁了。
    【解决方案3】:

    delete这个词在C++中有特殊的含义,所以deleted的使用是不幸的。

    MyObject my_object = MyObject(0);
    

    这一行声明一个MyObject 类型的对象使用自动存储持续时间创建(即,在堆栈上)。当作用域结束时,该对象将被销毁(即,将执行其析构函数)。标准中没有规定相关记忆的回忆(见后面的例子)。

    这个MyObject 类型的对象将使用表达式MyObject(0) 构造。此构造函数将初始化已分开供其专用的内存。

    注意:实际上,可以创建一个临时的,然后调用复制构造函数,但幸运的是,大多数编译器都避开了这个中间步骤,因为标准特别允许这样做。

    my_object = MyObject(1);
    

    这一行将由表达式MyObject(1) 确定的新值分配给已经存在的my_object 对象。为此,将创建一个具有自动存储持续时间的 MyObject 类型的临时文件。然后,执行赋值运算符;如果没有重载,它会将临​​时状态复制到my_object,擦除之前的状态。在表达式结束时,临时对象被破坏(再一次,没有为相关记忆的回忆做任何准备)。

    注意:MyObject(0) 没有被“删除”,因为它不存在,而是将其写入状态的内存重新用于从MyObject(1) 复制状态。


    正如所承诺的,既然这似乎是你的担心,那就讨论一下内存方面的问题。这是特定于编译器的,但大多数编译器的行为确实相似。

    假设我们有以下函数:

    void f() {
      MyObject my_object = MyObject(0);
    
      {
        my_object = MyObject(1);
        do_something(my_object);
      }
    
      {
        my_object = MyObject(2);
        do_something(my_object);
      }
    }
    

    堆栈需要多少空间?

    • 我们假设它在第一行执行直接构造
    • 我们假设编译器不够智能,无法执行堆栈着色(例如,Clang 不会)

    根据这些假设,它需要 3 个MyObject 的空间。

    • MyObject my_object = MyObject(0);:my_object需要活到函数结束
    • my_object = MyObject(1);: 临时需要创建
    • my_object = MyObject(2);: 临时创建需求

    堆栈空间在函数执行结束时被回收。

    如果编译器足够聪明地执行堆栈着色,那么两个临时对象(永远不需要一起)可以使用相同的内存点,从而将空间需求降低到 2 MyObject

    智能优化器还可以直接将MyObject(1)MyObject(2) 直接构建到my_object 中(如果它可以证明效果与构建临时然后复制它相同),从而降低空间要求为 1 MyObject

    最后,如果do_something的定义是可见的,并且它没有使用它的参数,那么在某些条件下它可以(理论上)完全绕过my_object的构造。这种优化可以通过简单的程序来见证,例如:

    int main() { int i = 0; for (; i < 1000; ++i); return i; }
    

    经过简单优化:

    int main() { return 1000; }
    

    (注意i 是如何消失的)

    您可能会注意到...实际上很难猜测编译器/优化器将能够做什么。如果您确实有严格的内存要求,那么(也许令人惊讶),您最好的选择可能是用函数替换块。

    【讨论】:

      【解决方案4】:

      第一行正在创建一个临时对象,并将该对象分配给 my_object。 在第 2 行中,创建了一个临时对象并将其分配给 my_object。

      所以只有一个对象 my_object

      我们不需要考虑临时对象。处理临时对象是编译器的责任。

      【讨论】:

      • 好的,听起来不错,那么编译器实际上是在删除它吗?我不想内存泄漏?
      • 如果未释放从堆分配的内存,则会发生内存泄漏。这里临时对象的内存是从堆栈中分配的。所以你不必担心内存泄漏
      • 所以我实际上是在做一个内存限制的算法问题。如果那块内存不是空闲的,我实际上会有 O(n lg n) 内存而不是 O(n),这会使我的解决方案失败。
      • @nivwusquorum:现在的编译器实际上非常擅长这一点,尤其是在优化构建中。这就是为什么难以调试优化代码的原因:内存被积极回收。
      猜你喜欢
      • 2011-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-09
      相关资源
      最近更新 更多