显式调用析构函数

49

我知道在大多数情况下,我们不应该显式地调用析构函数。然而,我在C++11标准N3485第13.4.5节的模板参数示例中看到了一个例外。

An explicit destructor call for an object that has a type that is a class template specialization may explicitly specify the template-arguments. Example:

template<class T> struct A {
    ~A();
}; 

void f(A<int>* p, A<int>* q) {
    p->A<int>::~A();      // OK: destructor call
    q->A<int>::~A<int>(); // OK: destructor call
}

在这种情况下,我认为我们可以显式地调用析构函数,请您向我解释一下吗?在这个例子中,这些析构函数调用意味着什么?为什么它们是合理的?

另一个问题:

除了实现placement delete时,我们还可以在哪些情况下显式地调用析构函数?

谢谢。

编辑:我从C++ FAQ中发现,我们不应该在局部变量上显式调用析构函数。


4
参考帖子是关于在局部变量上显式调用析构函数,该析构函数在其作用域结束时也会隐式地再次调用的内容。 - Andy Thomas
+1 我在我的代码中也做过类似的无聊事情,显式调用析构函数来销毁对象(或者至少看起来是这样)。但我是在托管类型上这样做的。看到答案会很有趣。 - Koushik Shetty
1
@taocp - 在这里澄清一下,就像引用帖子中所说的那样,未定义的行为并不是显式调用析构函数 - 而是导致在同一个对象上多次调用它。 - Andy Thomas
@AndyThomas-Cramer 嗯,我同意。我主要看了答案。我会尝试纠正它。谢谢! - taocp
@AndyThomas-Cramer:修正修正 :) UB 是指在调用已经不存在的对象的析构函数时发生的情况。对于具有非平凡析构函数的对象,它们停止存在的时刻是在调用析构函数时 - 但对于具有平凡析构函数的对象来说,这是在对象的存储被重用或释放时。 - Andy Prowl
显示剩余4条评论
2个回答

35

在这种情况下,我们似乎可以显式调用析构函数,你能解释一下为什么吗?

您是指为什么可以吗?因为语言允许在任何对象上进行显式析构函数调用。正如您所说,通常会产生未定义的行为,因为大多数对象将以其他方式被销毁,并且销毁两次任何内容(或更普遍地在销毁后访问它)都是未定义的行为。但这意味着您不能这样做,而不是语言将阻止您这样做。

还是您的意思是为什么要这样做?因为这是通过placement new创建的对象所需的方法。

在这个例子中,这些析构函数调用代表什么意思?

它们都表示同一件事,并等同于p->~A(); 它们调用了对象的析构函数。该示例演示了如果需要,可以在此处提供模板参数。我不确定您为什么要这样做。

除了placement delete之外,我们可以在哪些情况下显式调用析构函数?

我认为,只要您想,就可以随时调用平凡析构函数(不执行任何操作);但没有意义。我认为,销毁使用placement new创建的内容是唯一合法的原因。


1
+1。此外,您对于微不足道的析构函数是正确的,因为§3.8/1定义了具有微不足道析构函数的对象的生命周期并不会在调用析构函数时结束,而是在重新使用或释放该对象的存储空间时结束。 - Andy Prowl
@AndyProwl,你能回复我的评论吗?:) - Koushik Shetty
@AndyProwl 是的,没错 :) 只是澄清一下,如果您不在分配的对象上调用 delete 而是调用其析构函数,程序会有多好? - Koushik Shetty
@Koushik:嗯,在这种情况下,我真的不知道.NET如何处理这些垃圾收集等问题。如果您手动调用析构函数,然后不调用delete,并且对象是使用(非放置)new分配的,则会出现内存泄漏,而这几乎总是不好的事情。 - Andy Prowl
如果您有成员匿名联合体,并且其中的成员字段具有非平凡析构函数,则需要在类析构函数中手动调用这些成员字段的析构函数(因为编译器不知道要调用哪个析构函数)。 - Mark Ingram
显示剩余4条评论

31

在这种情况下,显式调用析构函数对我来说似乎是可行的,你能向我解释一下为什么吗?

因为语言允许在任何时候(假设您有访问权限,例如它不是私有析构函数),调用任何对象的析构函数。

在这个例子中,这些析构函数调用意味着什么?

它只是调用了析构函数。逻辑上,这意味着对象被销毁并应该从那时起被视为垃圾,不应再被引用或使用。技术上,它意味着对象处于析构函数留下的任何状态,对于某些对象而言可能与默认构造相同(但您绝不能依赖于此)。

为什么它们是合理的?

有时需要销毁对象而不释放其内存。这在很多类中都会发生,比如 variant/any、各种脚本绑定和反射系统、一些单例实现等。

例如,您可能会使用 std::aligned_storage 为对象分配缓冲区,然后使用就地 new 在该缓冲区中构建一个对象。您不能调用 delete 来删除此对象,因为这将同时调用析构函数,并尝试释放支持它的内存。在这种情况下,您必须显式调用析构函数才能正确销毁对象。

除了就地删除之外,我们可以在哪些情况下显式调用析构函数?

除了相应于就地 new 的运算符之外,实际上并没有所谓的“就地删除”(任何对 delete 的调用都会隐式地调用析构函数,除了编译器为失败的构造调用的那些调用,例如您对“就地删除”的概念)。

我提供一个例子,另一个例子是 std::vector。你可以调用成员函数,比如 pop_back()。这需要销毁向量中的最后一个元素,但不能使用 delete ,因为支持对象的内存是属于一个较大缓冲区的一部分,必须单独管理。很多其他容器也是如此,比如开放地址哈希表、deque等等。这就是使用 template typename 显式调用析构函数的一个例子。

对于库的用户来说,这是一个非常少用的功能,但是对于STL或者一些应用框架等底层库的实现者来说,会偶尔需要使用它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接