以下计划:
#include <iostream>
using namespace std;
class Test
{
public:
Test() { cout << "Constructor is executed\n"; }
~Test() { cout << "Destructor is executed\n"; }
};
int main()
{
Test(); // Explicit call to constructor
Test t; // local object
t.~Test(); // Explicit call to destructor
return 0;
}
打印以下输出:
Constructor is executed
Destructor is executed
Constructor is executed
Destructor is executed
Destructor is executed
我的问题是在main()中显式调用析构函数之后,为什么编译器在退出main()之前隐式调用析构函数?
作为一个附带问题,除了在delete运算符中使用之外还有其他任何使用明确调用析构函数的策略吗?
答案 0 :(得分:3)
您已经引入了未定义的行为。
按照标准:
§12.4析构函数
(11)隐式调用析构函数
(11.3) - 对于具有自动存储持续时间(3.7.3)的构造对象,当对象所在的块时 已创建退出(6.7),
和
15为对象调用析构函数后,该对象不再存在;如果是,行为是不确定的 为生命周期结束的对象调用析构函数(3.8)。 [例如:如果析构函数为 显式调用自动对象,随后以通常的方式保留该块 调用对象的隐式销毁,行为未定义。 - 例子]
您显式调用析构函数或通过调用t.~Test()
,然后在对象离开作用域时隐式调用它。这是未定义的。
该标准也提供了这个说明:
14 [注意:很少需要显式调用析构函数。这种调用的一种用途是针对特定的对象 使用放置new-expression的地址。这样使用显式放置和破坏对象即可 有必要处理专用硬件资源和编写内存管理设施。
答案 1 :(得分:2)
这不是对构造函数&#34;的显式调用:
Test(); // Explicit call to constructor
它构造一个临时对象,它隐式调用构造函数,然后临时对象立即超出范围,隐式调用析构函数。您无法显式调用构造函数,在构造对象时会隐式调用它。
我的问题是在main()中显式调用析构函数之后,为什么编译器在退出main()之前隐式调用析构函数?
因为编译器总是破坏局部变量。仅仅因为你做了一些愚蠢的事情(手动摧毁了一个自动销毁的对象)并没有改变它。
作为一个附带问题,除了在delete运算符中使用之外还有其他任何使用明确调用析构函数的策略吗?
在管理原始内存中对象的生命周期时使用它,这是由std::vector
等容器和std::optional
等其他实用程序完成的。
答案 2 :(得分:1)
我的问题是在main()中显式调用析构函数之后,为什么编译器在退出main()之前隐式调用析构函数?
当对象超出范围时,将调用析构函数,无论是否显式调用析构函数。不要为具有自动存储持续时间的对象显式调用析构函数。
作为一个附带问题,除了在delete运算符中使用之外还有其他任何使用明确调用析构函数的策略吗?
是。使用placement new
expression初始化对象时,需要显式调用析构函数。上述网站的示例代码:
char* ptr = new char[sizeof(T)]; // allocate memory
T* tptr = new(ptr) T; // construct in allocated storage ("place")
tptr->~T(); // destruct
delete[] ptr; // deallocate memory
答案 3 :(得分:0)
t的范围是主要功能。它在堆栈上创建,并在函数结束时被销毁。
它应该如何工作,当你早点打电话给析构函数时,你就不会改变它。
你不需要调用析构函数,在这种情况下这样做会导致它被调用两次。
如果您使用
Test* t = new Test();
析构函数不会在main的末尾自动调用。
答案 4 :(得分:0)
编译器不关心您是否明确调用了析构函数。本地对象t
超出范围,这就是它被销毁并调用析构函数的原因。
明确地调用析构函数是不好的做法。相反,您应该编写一个类似cleanup()
的方法,可以显式调用,也可以在析构函数中调用。如果你想避免可以调用两次清理,可以在你的类中添加这样的东西:
private:
bool cleanupPerformed;
void cleanup()
{
if( !cleanupPerformed )
{
// ... do cleanup work here ...
cleanupPerformed = true;
}
}