即使在显式调用析构函数后也会自动销毁对象

时间:2017-03-14 17:50:47

标签: c++ oop

以下计划:

#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运算符中使用之外还有其他任何使用明确调用析构函数的策略吗?

5 个答案:

答案 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;
   }
}