#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对象和没有-在调用析构函数之后)。