【问题标题】:c++ destroy an object on stack framec ++销毁堆栈框架上的对象
【发布时间】:2013-09-07 03:27:39
【问题描述】:

我试图了解当对象在堆栈上销毁时会发生什么。 这是我的示例代码:

#include <stdio.h>
struct B {
  ~B() {puts("BBBB");}
};

int main()
{
    B b;
    b.~B();
}

输出是

BBBB
BBBB

根据输出,我可以看出对象被销毁了两次。一个是 ~B(),另一个是在“}”之后。一个对象如何以及为什么会被破坏两次?

更新: 在我查看回复后,我认为析构函数不会破坏这个对象。它有一种方法可以在对象超出范围“}”之前将其销毁。 谢谢

【问题讨论】:

标签: c++


【解决方案1】:

您不应该手动调用析构函数。发生的情况是您正在调用析构函数,然后当对象从堆栈中弹出时,编译器会再次自动调用析构函数。

【讨论】:

    【解决方案2】:

    ~B() 被调用之前销毁

    析构函数通常用于释放内存并进行其他清理 当对象被销毁时,用于类对象及其类成员。 当类对象消失时,会为该类对象调用析构函数 范围或被明确删除。 Source

    所以你只是调用了两次函数。

    【讨论】:

      【解决方案3】:

      在 C++ 中有最少甚至没有垃圾收集,对象在超出范围时被简单地销毁。因此,您可以将测试替换为:

      #include <iostream>
      
      struct B {
          B() { std::cout << "B()" << std::endl; }
          ~B() { std::cout << "~B()" << std::endl; }
      };
      
      int main()
      {
          std::cout << "start main" << std::endl;
          { // scope
              std::cout << "start scope" << std::endl;
              B b;
              std::cout << "end scope" << std::endl;
          } // <-- b gets destroyed here.
          std::cout << "end main" << std::endl;
      }
      

      如果您希望堆栈上的对象可以控制其生命周期,则可以执行以下操作:

      #include <iostream>
      #include <memory.h>
      
      struct B {
          B() { std::cout << "B()" << std::endl; }
          ~B() { std::cout << "~B()" << std::endl; }
      };
      
      int main()
      {
          std::cout << "start main" << std::endl;
          { // scope
              std::cout << "start scope" << std::endl;
              void* stackStorage = alloca(sizeof(B));
              std::cout << "alloca'd" << std::endl;
      
              // use "in-place new" to construct an instance of B
              // at the address pointed to by stackStorage.
              B* b = new (stackStorage) B();
              std::cout << "ctord" << std::endl;
      
              b->~B(); // <-- we're responsible for dtoring this object.
              std::cout << "end scope" << std::endl;
          } // <-- b gets destroyed here, but it's just a pointer.
          std::cout << "end main" << std::endl;
      }
      

      现场演示:http://ideone.com/ziNjkd

      但请记住,它是堆栈。当它超出范围时,它就会消失 - 如果你不销毁它,它就会消失。

      {
          void* stackStorage = alloca(sizeof(B));
          B* b = new (stackStorage) B(); // "in-place new"
      } // (*b)s memory is released but (*b) was not dtord.
      

      【讨论】:

      • 获取堆栈空间的标准方法是使用std::aligned_storage——例如声明一个std::aligned_storage&lt;sizeof(B)&gt;::type 类型的对象,然后使用placement new 在它的地址构造你的对象。
      【解决方案4】:

      您手动调用析构函数的唯一时间是当您有理由使用placement new

      【讨论】:

        【解决方案5】:

        请记住,析构函数与任何其他函数一样。与其他函数的唯一区别是它在对象被释放时自动调用。你不能把它看作一个事件。你有机会在物体被歼灭之前清理一切。手动调用析构函数不会释放对象。

        【讨论】:

          【解决方案6】:

          C++ 中的对象构造/销毁遵循这个简单的规则:

          1. 任何自动分配(和构造)的东西都会被自动销毁。

          2. 任何用new 明确分配的东西都会通过delete 明确销毁。

          3. 使用new() 显式构造的任何内容都必须通过调用析构函数显式销毁。

          在所有三种情况下都必须调用析构函数,区别在于对象的内存分配方式:

          1. 对象在栈上,它的分配由编译器管理。

          2. 对象在堆上,它的分配由程序员管理。

          3. 对象在任何地方,构造和销毁与分配无关。

          在前两种情况下,我们结合了分配和构造,因此结合了销毁和释放。第三种情况完全不同,但语言完全支持,以及允许显式调用析构函数的原因;因为在这种情况下,一个对象是在没有为其分配内存的情况下构造的。因此,它必须在不释放内存的情况下也是可破坏的。这种情况可以这样使用:

          void* buffer = (void*)new char[sizeof(Foo)];  //allocation
          
          Foo* myFoo = new(buffer) Foo();  //construction
          myFoo->~Foo();  //destruction
          
          Foo* anotherFoo = new(buffer) Foo();  //reuse the buffer to construct another object in it
          anotherFoo->~Foo();  //destruction of the second object
          
          delete buffer;  //deallocation
          

          注意,这实际上构造了两个对象,一个接一个地在同一个地方,在内存被重用之前显式地销毁它们。缓冲区也可以是一大块内存来存储许多对象,std::vector&lt;&gt; 就是这样工作的。

          所以,是的,破坏确实会破坏您的对象,您不能在破坏后使用它。但是由于您在问题中使用了自动分配和构造的对象,因此编译器还负责破坏它,从而导致双重破坏。这始终是一个错误,对象永远不能被破坏两次,但语言允许你这样做。

          【讨论】:

          • 感谢您的回复。如果破坏确实破坏了我的对象,那将违反您的规则#1“任何自动分配(和构造)的东西都会被自动破坏。”因为对象是自动分配的,不应手动销毁(b.~B())。对吗?
          • 是的,如果您使用new(buffer) B() 构造了它,则只能显式调用b-&gt;~B()。不幸的是,语言并不能阻止你做错事,你有责任不让被破坏的对象超出范围或调用delete
          【解决方案7】:

          您所做的是一个很好的变量超出范围的示例。

          int main()
          {
            //Main is started
          
            B b;
            /* What happens is that `b` is constructed as a local 
               variable and put on the runtime stack.*/
          
            b.~B();
            /*You then invoke the destructor manually which is bad practice. 
              But it is possible and it will execute the code. 
              However, the state of the resulting instance is undefined.*/
          }
          /*Main ends and local variables defined in the 
            current scope get destroyed. Which also means that B's destructor 
            is called the second time */
          

          仅供参考 - 您应该手动销毁对象的唯一时间是当它像这样放在堆上时:

          // create an instance of B on the heap
          B* b = new B();
          
          // remove instance and implicitly call b's destructor.
          delete b;
          

          【讨论】:

            【解决方案8】:

            我想回答我自己的问题。

            看了很多遍,下面是我的总结。

            1. destructor doesnt destroy  it's object.  the object stays at stack
            until out of scope.
            2. nothing can destroy a stack object. 
            3. the destructor did destroy RESOURCE inside the object.
            

            示例代码:

             struct B {
               B() {happy="today is a good day"; hour = 7;}
              ~B() {puts("BBBB");}
               std::string happy;
               int hour;
               };
            
            int main()
            {
                B b;
                std::cout << b.happy << b.hour <<std::endl;
                b.~B();
                std::cout << b.happy << b.hour <<std::endl;
            
            }
            

            输出:

            today is a good day7
            BBBB
            7
            BBBB
            

            我们可以看到在调用 b.~B() 之后资源 b.happy 消失了。这是我第 3 点的证明。

            你看 b.hour(int type is not a resource) 还在这里。这是我第 1 点的证明。

            【讨论】:

            • 我要补充: 4. 多次销毁一个对象是非法的,在对象被销毁后使用它(例如打印它的成员)也是非法的。这可能是未定义的行为。此外,它似乎“工作”,但它会导致严重的内存损坏ideone.com/LzxW27 (哇,ideone 刚刚在输入时重新换了皮肤!)我>
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-06-22
            • 2021-11-03
            • 2023-02-08
            • 2011-09-18
            • 2012-04-26
            相关资源
            最近更新 更多