手动调用析构函数

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