直接调用(虚拟)析构函数是不是有效?

Posted

技术标签:

【中文标题】直接调用(虚拟)析构函数是不是有效?【英文标题】:Is it valid to directly call a (virtual) destructor?直接调用(虚拟)析构函数是否有效? 【发布时间】:2011-08-27 12:34:06 【问题描述】:

在this answer 中,Ryan 直接调用了虚拟析构函数。我已经在 VS2010 中测试了代码,它正确地调用了所有的析构函数(用日志语句测试)。这样做真的有效吗?这种方法有什么问题、缺陷甚至优点?

我只能将其视为一种真正强制重置实际类型的方法,即使它们没有覆盖虚拟 reset 函数,因为它们至少必须在其析构函数中进行清理。

另外,调用析构函数究竟会带来什么样的副作用?在这样的析构函数调用之后使用对象是未定义的行为吗?如果立即通过new (this) MyClass(); 调用重新初始化它会怎样?

【问题讨论】:

适用于直接析构函数调用的规则与适用于使用 delete 的规则相同。 new (this) MyClass(); 基本上是在调用析构函数(即重构它)之后你可以对对象做的唯一事情。 @dribeas: 甚至这有点可疑 - 如果构造函数不能失败,那么你就可以逃脱它(尽管对于非 POD 类的 IIRC,你仍然使指针和对对象的引用无效,虽然我可能记错了)。但是那个问题是关于“数千行代码”的,所以我猜其中一个可能会失败。您留下了一个未构造的对象。所以你不能从reset()返回或扔掉,因为如果你这样做了,那么以后有人会(无效地)试图摧毁它。可能你可以terminate(). @SteveJessop "你仍然使指向对象的指针和引用无效,虽然我可能记错了"你记错了 @curiousguy:我想我指的是 C++03 的 3.8/7:这些引用仍然有效的条件非常复杂。 【参考方案1】:

手动调用析构函数是完全有效的,不管它是否是虚拟的。您只想确保每次构造函数调用都只调用一次。

Is it undefined behaviour to use the object after such a destructor call? 

是的。

What if one immediatly reinitializes it with a new (this) MyClass(); call?

仍然可怕地未定义。

除非您必须手动放置对象,否则请勿手动破坏对象,例如使用放置新的或等效的位置,绝对不要像那样重新初始化被破坏的对象,并希望避免 UB。像 std::vector 这样的类非常明确地使访问被破坏的对象 UB,即使你在它的位置创建一个新元素,它仍然是 UB。

【讨论】:

"它只被调用一次" + "对于每个构造函数调用" = +1 所以实际上这样做是未定义的行为,因为任何对象都将在堆栈上清理或之后通过删除调用? (除了内存泄漏。) @Xeo:不正确。您只需要确保之前调用过构造函数的每个析构函数。虽然这通常还涉及到在某些时候使用放置 new 来完成。 @Xeo:如果你重建它就不会:type x; ~x; new (&x) type(); @John Dibling:已添加,我是一名代表:P【参考方案2】:

一个涉及一个且仅一个结构的有效使用示例:

typedef boost::aligned_storage<
    sizeof(T), boost::alignement_of<T>::value>::type arena_type;
arena_type arena;
T* p = new (&arena) T();
p->~T();
// Don't touch p now

这可能很有用,例如实现变体类型(警告:异常安全留给读者练习)。 C++0x 无限制联合对类类型也有类似的用途。

请注意,对于类类型,如果您调用了析构函数,则上述将是 UB。

【讨论】:

为什么你现在不能再做一个新的展示位置?是因为放置 new 返回一个指针 p 可能与 &amp;arena 不同吗?我想是的,但这让我感到困惑。 @Flamefire 如果你足够小心,你可以重复使用竞技场来构造不同的对象(并且不要忘记将它与适当的析构函数调用配对) 我把它放在一个单独的问题中,汇总了我能找到的所有内容:***.com/questions/48707481/… 重点是你的“小心”。 “足够小心”似乎非常困难【参考方案3】:

只要您在预先分配的 POD 内存块之上调用 placement new,释放调用任何析构函数(无论是否为虚拟的)都完全有效。

placement new 和显式释放器调用只会调用引用区域中的构造函数和析构函数,因此内存分配有效地被排除在对象生命周期之外

【讨论】:

以上是关于直接调用(虚拟)析构函数是不是有效?的主要内容,如果未能解决你的问题,请参考以下文章

能直接调用析构函数,不能直接调用构造函数

有没有自动化的方法来实现构造函数后和析构函数前的虚拟方法调用?

默认的虚拟析构函数是不是会阻止编译器生成的移动操作?

C++ 虚拟析构函数 (virtual destructor)

有没有办法调用纯虚拟类的“删除析构函数”?

根据定义,将“虚拟析构函数放入接口”是不是不再使其不再是接口?