如何删除作为 C++ 联合的成员变量的字符串对象?

How to delete a string object which is a member variable of a C++ union?

当我在阅读联合体中的构造函数和析构函数时,我遇到了一个堆栈溢出问题

该问题的公认答案是我们需要显式地为字符串对象提供析构函数。但是对于Deleting string object in C++的接受答案,我们不应该显式删除字符串对象,当字符串超出范围时,它的析构函数将被自动调用并释放内存。

这两个是矛盾的。我可以知道即使我们需要如何删除字符串吗?以及我们需要明确删除哪些对象?

根据规范如果联合的任何非静态数据成员具有非平凡的默认构造函数 (12.1)、复制构造函数 (12.8)、移动构造函数 (12.8)、复制赋值运算符 (12.8)、移动赋值运算符(12.8),或析构函数(12.4),联合对应的成员函数必须由用户提供,否则将被隐式删除(8.4.3)为联合。但是不平凡是什么意思?

微不足道的constructor/destructor就是什么都不做。本质上,如果你的析构函数看起来像

~Type() {}

那就麻烦了。 std::string 的析构函数不是那样空的。它必须清理字符串可能分配的所有内存。

因此,由于联合的析构函数不执行任何操作,但 std::string 需要执行某些操作(否则会出现内存泄漏),因此您必须为联合提供一个析构函数来调用字符串的析构函数,以便它得到了正确的清理。

在D&D中,我们称这种情况为"Specific Beats General"。

在第二个链接问题中,建议 "Don't manually delete a std::string" 是正确的,因为它是一般建议:分配在堆栈上的对象不应该手动调用它们的析构函数,因为当他们超出了范围。如果你这样写:

void do_a_thing() {
    std::string str = "Wheeeeeeeeee";
    str.~();
}//Double-delete == BOOM!

结果会发生不好的事情,因为str分配的内存会被双删。

但是在第一个相关问题中,您专门处理工会问题,因此建议 "you need to delete objects stored in a union" 是具体建议。联合不会自动清理它们的对象,因为它们无法在编译时知道包含了哪个对象。

void do_a_thing() {
    union {
        int i;
        std::string str;
    } var;
    var.str = "Wheeeeeeeee";
}//Uh oh, str's destructor was never called, and we have a memory leak!

而且,俗话说 "Specific beats General",这意味着您需要用 "always manually delete objects in unions" 的建议取代 "never manually delete a std::string object" 的建议。


当然,我们可以让这一切都变得毫无意义。你知道如何? std::variant,在 C++17 中引入。 std::variant 基本上是一个类型安全的联合,禁止访问非活动成员,并确保正确清理对象。

void do_a_thing() {
    std::variant<std::string, int> variant = std::string("Wheeeeeeee");
}//variant properly cleans up the memory, no hassle involved

当然,您确实需要了解 Visitor Pattern 才能简洁地使用 std::variant,但是由于类型安全的好处,这几乎肯定是值得的。