C++如何销毁栈帧上的对象?

3

我想理解当一个对象在栈上销毁时会发生什么。 以下是我的示例代码:

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

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

输出结果为

BBBB
BBBB

根据输出,我可以看出这个对象被销毁了两次。一个是在~B()函数中,另一个是在"}"之后。为什么一个对象会被销毁两次?如何发生的?
更新: 在查看回复后,我认为析构函数并没有销毁这个对象。有没有一种方法可以在对象超出作用域"{}"之前销毁它呢? 谢谢。

可能是当C++析构函数被调用时?的重复问题。 - Jacob Krall
8个回答

4
你不应该手动调用析构函数。实际发生的情况是你已经调用了析构函数,然后当对象从栈中弹出时,编译器会自动再次调用析构函数。

4

~B() 在对象被销毁之前被调用。

当一个对象超出作用域或被显式删除时,通常使用析构函数来释放内存并为类对象及其成员进行其他清理。析构函数会在类对象被销毁时被调用。 来源

因此,您只是调用函数两次。


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<sizeof(B)>::type的对象,然后使用放置new在其地址处构造您的对象。 - Benjamin Lindley

2

1

请记住,析构函数与其他函数一样。唯一的区别是它在对象被释放时自动调用。你可以将其看作一个事件。你有机会在对象被销毁之前清理一切。手动调用析构函数不会释放对象。


1

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<> 就是这样工作的。
因此,是的,析构确实会销毁您的对象,您不能在销毁后使用它。但由于您在问题中使用了自动分配和构造的对象,编译器也负责销毁它,导致了双重销毁。这总是一个错误,对象绝不能被销毁两次,但语言仍允许您这样做。

谢谢您的回复。如果销毁操作破坏了我的对象,那么这将违反您的第一原则:“任何自动分配(和构造)的内容都会被自动销毁。”因为该对象是自动分配的,所以不应该手动销毁 (b.~B()),对吧? - cppython
是的,如果您使用new(buffer) B()构造了对象,那么您必须显式调用b->~B()。不幸的是,语言无法阻止您犯错,因此您有责任不让已析构的对象超出范围或调用delete - cmaster - reinstate monica

0

你所做的是一个变量超出作用域的很好的例子。

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;

0
我想回答自己的问题。经过大量阅读,这是我的总结。
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类型不是资源)仍然存在。这证明了我的第1点观点。


1
我想补充一点:4. 多次销毁一个对象是非法的,使用已经被销毁的对象(例如在此处打印其成员)也是如此。这可能是未定义行为。此外,它似乎“可以工作”,但会导致严重的内存损坏:http://ideone.com/LzxW27 (哇,ideone 在我输入时刚刚换了皮肤!) - gx_

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接