手动调用析构函数
Manual call of destructor
#include <iostream>
using namespace std;
namespace GB
{
class Test
{
public:
Test() { cout << "Constructor is executed\n"; }
~Test() {
cout << i << " " << "Destructor is executed\n";
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)在析构函数的第一次调用中,memeber 值从 6 更改为 7,在第二次调用中仍然为 6.
3)我们可以停止第二次调用析构函数吗(我只想保留手动调用析构函数)。
1)Why destructor is called twice.
因为它是 C++ 而不是 Pascal,并且语言的工作是在对象死亡时(即当它们的生命周期结束时)销毁对象。对象生命周期是任何语言语义不可或缺的一部分,它们通常是区分语言的因素,因此您不能仅仅假设 C++ 的行为与您正在考虑的其他语言一样。由于语言在对象的生命周期结束于范围末尾({
块 }
)时会破坏该对象,因此第二个冗余调用是 earlier 那个你手动写的。在某种程度上,C++ 的全部意义在于为您解决这个问题:如果您认为需要手动销毁东西,通常您不会想到 C++。
2)In first call of destructor memeber value is changed from 6 to 7 , still in second call it comes as 6.
对象在销毁后不复存在,因此在析构函数中对对象状态的更改将丢失。析构函数的第二次调用是未定义的行为,因此它可以做“任何事情”。编译器可能优化了析构函数中的 this->i = 7
赋值,因为该赋值没有可以在其外部观察到的副作用:您编写了死代码,不要惊讶它被视为死代码。
3)Can we stop second call of destructor (I want to keep only manually call of destructor).
没有。 C++ 不是 Pascal。重点是析构函数 运行 自动。这是 C++ 与许多其他面向对象语言之间的主要语义差异:C++ 析构函数也与 Java 或 .Net (C#) 中的终结器完全不同。如果你想更早地销毁对象,适当地限制对象的范围。忘记手动调用析构函数。
换句话说:停止将析构函数视为您调用的函数。相反,想想如何管理对象的生命周期,以便在您认为方便的时候销毁它。
Why destructor is called twice.
第一个电话来自 i.~Test();
.
行
第二次调用是当变量 i
超出范围时(从 main
返回之前)自动调用析构函数。
In first call of destructor memeber value is changed from 6 to 7 , still in second call it comes as 6.
这是由未定义的行为引起的。当对象的析构函数被调用两次时,您应该预料到未定义的行为。当程序进入未定义的行为领域时,不要试图使逻辑有意义。
Can we stop second call of destructor (I want to keep only manually call of destructor).
当变量超出范围时,您不能禁用对自动变量的析构函数的调用。
如果要控制何时调用析构函数,请使用动态内存创建对象(通过调用 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();
允许您控制何时删除基础对象。
我想补充其他优秀的答案。
通过使用 union 可以在不使用堆的情况下显式调用对象的构造函数和析构函数。这是一个例子:
namespace GB
{
class Test
{
public:
Test() { cout << "Constructor is executed\n"; }
~Test() {
cout << i << " " << "Destructor is executed\n";
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 对象和没有 -在调用析构函数之后)。
#include <iostream>
using namespace std;
namespace GB
{
class Test
{
public:
Test() { cout << "Constructor is executed\n"; }
~Test() {
cout << i << " " << "Destructor is executed\n";
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)在析构函数的第一次调用中,memeber 值从 6 更改为 7,在第二次调用中仍然为 6.
3)我们可以停止第二次调用析构函数吗(我只想保留手动调用析构函数)。
1)Why destructor is called twice.
因为它是 C++ 而不是 Pascal,并且语言的工作是在对象死亡时(即当它们的生命周期结束时)销毁对象。对象生命周期是任何语言语义不可或缺的一部分,它们通常是区分语言的因素,因此您不能仅仅假设 C++ 的行为与您正在考虑的其他语言一样。由于语言在对象的生命周期结束于范围末尾({
块 }
)时会破坏该对象,因此第二个冗余调用是 earlier 那个你手动写的。在某种程度上,C++ 的全部意义在于为您解决这个问题:如果您认为需要手动销毁东西,通常您不会想到 C++。
2)In first call of destructor memeber value is changed from 6 to 7 , still in second call it comes as 6.
对象在销毁后不复存在,因此在析构函数中对对象状态的更改将丢失。析构函数的第二次调用是未定义的行为,因此它可以做“任何事情”。编译器可能优化了析构函数中的 this->i = 7
赋值,因为该赋值没有可以在其外部观察到的副作用:您编写了死代码,不要惊讶它被视为死代码。
3)Can we stop second call of destructor (I want to keep only manually call of destructor).
没有。 C++ 不是 Pascal。重点是析构函数 运行 自动。这是 C++ 与许多其他面向对象语言之间的主要语义差异:C++ 析构函数也与 Java 或 .Net (C#) 中的终结器完全不同。如果你想更早地销毁对象,适当地限制对象的范围。忘记手动调用析构函数。
换句话说:停止将析构函数视为您调用的函数。相反,想想如何管理对象的生命周期,以便在您认为方便的时候销毁它。
Why destructor is called twice.
第一个电话来自 i.~Test();
.
行
第二次调用是当变量 i
超出范围时(从 main
返回之前)自动调用析构函数。
In first call of destructor memeber value is changed from 6 to 7 , still in second call it comes as 6.
这是由未定义的行为引起的。当对象的析构函数被调用两次时,您应该预料到未定义的行为。当程序进入未定义的行为领域时,不要试图使逻辑有意义。
Can we stop second call of destructor (I want to keep only manually call of destructor).
当变量超出范围时,您不能禁用对自动变量的析构函数的调用。
如果要控制何时调用析构函数,请使用动态内存创建对象(通过调用 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();
允许您控制何时删除基础对象。
我想补充其他优秀的答案。
通过使用 union 可以在不使用堆的情况下显式调用对象的构造函数和析构函数。这是一个例子:
namespace GB
{
class Test
{
public:
Test() { cout << "Constructor is executed\n"; }
~Test() {
cout << i << " " << "Destructor is executed\n";
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 对象和没有 -在调用析构函数之后)。