手工调用析构函数


#include <iostream> 
using namespace std; 
namespace GB
{
class Test 
{ 
public: 
Test()  { cout << "Constructor is executedn"; } 
~Test() { 
cout << i << " " << "Destructor is executedn";  
this->i = 7;
} 
int i = -1;
}; 
}

int main() 
{ 
// Test();  // Explicit call to constructor 
GB::Test t;    // local object 
t.i = 6;
t.~Test(); // Explicit call to destructor 
return 0; 
}

Constructor is executed
6 Destructor is executed
6 Destructor is executed

我的问题是:
1)为什么析构函数调用两次。
2)第一次调用析构函数时,成员值从6更改为7,第二次调用时,成员值仍然为6。
3)我们可以停止对析构函数的第二次调用吗(我想只手动调用析构函数)

为什么析构函数调用两次

第一个调用来自i.~Test();
第二个调用是当变量i超出作用域(在从main返回之前)时自动调用析构函数

第一次调用析构函数时,成员值由6变为7,第二次调用时,成员值仍为6。

这是由未定义行为引起的。当对象的析构函数被调用两次时,你应该预料到未定义的行为。当程序进入未定义的行为领域时,不要试图在逻辑上讲得通。

我们可以停止对析构函数的第二次调用吗(我想只手动调用析构函数)

当变量超出作用域时,不能禁用对自动变量析构函数的调用。

如果你想控制何时调用析构函数,使用动态内存创建一个对象(通过调用new Test),并通过调用delete销毁该对象。

GB::Test* t = new GB::Test(); // Calls the constructor
t->i = 6;
delete t;                     // Calls the destructor

即使在这种情况下,显式调用析构函数几乎总是错误的。

t->~Test();  // Almost always wrong. Don't do it.

请注意,如果你想使用动态内存创建对象,最好使用智能指针。例如

auto t = std::make_unique<GB::Test>();  // Calls the constructor
t->i = 6;
t.reset();                              // Calls the destructor

如果忽略了t.reset();,则动态分配对象的析构函数将被调用,并且当t超出作用域时内存将被释放。t.reset();允许您控制何时删除底层对象。

1)为什么析构函数调用两次

因为它是c++而不是Pascal,并且当对象死亡时(即当它们的生命周期结束时)销毁对象是语言的工作。对象生命周期是任何语言的语义的一个组成部分,它们通常是语言之间的区别,所以您不能仅仅假设c++的行为会像您正在考虑的其他语言一样。由于当对象的生命周期在作用域的末尾结束时({}),语言会销毁对象,因此第二个冗余调用是您手动编写的较早的调用。c++的全部意义就在于,在某种程度上,为你解决这个问题:如果你认为你需要手动销毁东西,你通常不会考虑c++。

2)第一次调用析构函数时,成员值从6变为7,第二次调用时,成员值仍为6。

对象在析构函数之后不再存在,因此在析构函数中对对象状态的更改将丢失。析构函数的第二次调用是未定义的行为,因此它可以做"任何事情"。编译器很可能优化了析构函数中的this->i = 7赋值,因为该赋值没有在析构函数之外可观察到的副作用:你写了死代码,不要惊讶它被当作死代码对待。

3)我们可以停止对析构函数的第二次调用吗(我想只手动调用析构函数)

。c++不是Pascal。关键在于析构函数是自动运行的。这就是c++和许多其他面向对象语言之间的主要语义区别:c++析构函数也不像Java或。net (c#)中的终结器。如果您希望更早地销毁对象,请适当地限制对象的作用域。忘记手动调用析构函数吧。

换句话说:不要把析构函数看作是你调用的函数。相反,应该考虑如何管理对象的生命周期,以便在您认为方便的时候销毁它。

我想补充一下其他优秀的答案。

可以通过使用联合显式调用对象的构造函数和析构函数,而不使用堆。下面是一个例子:

namespace GB
{
class Test
{
public:
Test()  { cout << "Constructor is executedn"; }
~Test() {
cout << i << " " << "Destructor is executedn";
this->i = 7;
}
int i = -1;
};
union OptTest
{
OptTest() : test() {}  // if there is no need to control the construction
~OptTest() {}
Test test;
char none;
};
}
int main()
{
// EDIT: The following comment appears in the original question,
//       but it is misleading. Test() will create a temporary object,
//       which will be immediately destroyed. Use placement-new to
//       explicitly call a constructor instead.
// Test();  // Explicit call to constructor
GB::OptTest ot;    // local object
ot.test.i = 6;
ot.test.~Test(); // Explicit call to destructor
return 0;
}

这是不是一个好主意取决于用例。例如,如果你想实现std::optional<Test>之类的东西,那么使用union是控制Test销毁的好方法。

注意:none字段不是严格要求的,但在我看来,它更好地传达了union可能处于两种状态之一的事实(有Test对象和没有-在调用析构函数之后)。

最新更新