【问题标题】:Is a Union Member's Destructor Called工会成员的析构函数是否被调用
【发布时间】:2017-02-27 15:36:47
【问题描述】:

C++11 允许在 union: Member of Union has User-Defined Constructor 中使用标准布局类型

然后我的问题是:当union 超出范围时,我是否保证会调用自定义析构函数?

我的理解是切换时必须手动销毁和构造:http://en.cppreference.com/w/cpp/language/union#Explanation

但是像这样的例子呢:

{
    union S { string str;
              vector<int> vec;
              ~S() {} } s = { "Hello, world"s };
}

s 超出范围时,我是否因为没有调用string 的析构函数而泄漏了堆上分配的字符串的内存?

【问题讨论】:

  • 这甚至不能编译。 AFAIK 你需要对这些东西使用placement new 并手动调用构造函数。所以为了保持一致,析构函数也是如此。
  • @Hayt 所以在我编辑之后,这个构造函数将编译:ideone.com/Cf0OOQ 但根据Nathan Oliver's answer确实泄漏。
  • 我不知道为什么这项作业一开始就有效。这似乎是无意的。但它仍然泄漏。你可以在这里看到 basic_string 构造函数被调用,但不是析构函数:godbolt.org/g/2uvrWn
  • 是的,你应该做什么和必须做什么。它与{ } 分配一起使用的情况似乎是联合的设计缺陷,因为它隐式分配而不是解除分配。
  • 我也同意 nathan 是正确的。但如果它有帮助,我可以添加一些解释的答案。

标签: c++ c++11 destructor unions standard-layout


【解决方案1】:

在您提供的示例中,str 不会被破坏。 [class.union]/2 中的标准状态

联合可以具有成员函数(包括构造函数和析构函数),但不能具有虚拟 (10.3) 函数。联合不应有基类。联合不应用作基类。如果联合包含引用类型的非静态数据成员,则程序格式错误。最多一个联合的非静态数据成员可以有一个大括号或相等初始化器。 [ 注意:如果联合的任何非静态数据成员具有非平凡的默认构造函数 (12.1)、复制构造函数 (12.8)、移动构造函数 (12.8)、复制赋值运算符 (12.8)、移动赋值运算符 ( 12.8)或析构函数(12.4),联合的相应成员函数必须是用户提供的,否则它将为联合隐式删除(8.4.3)。 — 尾注 ]

强调我的

因此,由于 strvec 都具有重要的特殊成员函数,因此您需要自己为联合提供它们。

请注意,根据bogdan's cmets,下面的空析构函数是不够的。在 [class.union]/8 我们有

[...]如果 X 是联合,则其变体成员是非静态数据成员;[...]

所以这个联合的所有成员都是变种。然后,如果我们查看 [class.dtor]/8 我们有

在执行析构函数的主体并销毁主体内分配的任何自动对象后,类 X 的析构函数调用 X 的直接非变体非静态数据成员的析构函数[...]

因此析构函数不会自动销毁联合成员,因为它们是变体。

你可以像 kennytm 一样创建一个 tagged union here

struct TU {
   int type;
   union {
     int i;
     float f;
     std::string s;
   } u;

   TU(const TU& tu) : type(tu.type) {
     switch (tu.type) {
       case TU_STRING: new(&u.s)(tu.u.s); break;
       case TU_INT:    u.i = tu.u.i;      break;
       case TU_FLOAT:  u.f = tu.u.f;      break;
     }
   }
   ~TU() {
     if (tu.type == TU_STRING)
       u.s.~string();
   }
   ...
};

确保正确的成员被销毁或只使用std::variantboost::variant

【讨论】:

  • @JonathanMee 我可以确保您删除了复制构造函数。该标准甚至在下一段中有一个带有std::sting 的联合示例,并且它声明 由于 std::string (21.3) 声明了所有特殊成员函数的非平凡版本,U 将具有隐式删除的默认构造函数、复制/移动构造函数、复制/移动赋值运算符和析构函数。要使用 U,这些成员函数中的部分或全部必须由用户提供
  • @JonathanMee 在这些示例中没有任何编译器有罪。该初始化不涉及联合的复制构造函数;该联合是一个聚合,这就是聚合初始化(它确实涉及第一个联合成员 string 的复制初始化,它确实调用了 string 的复制构造函数)。
  • @JonathanMee 这些构造函数确实被删除了,但这并不一定意味着不可能构造。一个聚合仍然可以通过聚合初始化来初始化,即使它的所有构造函数都被删除了。这不仅限于工会。 特定于联合的是联合聚合初始化被定义为初始化第一个成员。尝试将用户提供的构造函数添加到该联合;比如说,像S() { } 这样的默认构造函数。联合将不再是聚合,初始化将不得不使用构造函数,这将失败。
  • @NathanOliver 我认为避免它们是绝对正确的;-)。话虽如此,我认为您使用的引用不能解释为什么 OP 的代码不调用 string 的析构函数;毕竟,是的,联合的析构函数会被隐式定义为删除,但他提供了一个;为什么它不起作用?我想说更好的报价是[class.dtor]/8;关键字是non-variant。变体成员的定义在[class.union.anon]/4 中(工会的成员是变体成员)。
  • @JonathanMee 由于我在上面给出的报价,就标准而言。实际上,因为析构函数不会(总是)知道要调用哪个子对象析构函数。
【解决方案2】:

您的示例无法编译。默认情况下,联合有一个已删除的析构函数。因为当然,应该调用什么析构函数?当然,你不能同时调用两者。并且任何地方都没有存储有关实际构造哪个成员的任何信息。提供适当的析构函数取决于您。

这是尝试编译代码 sn-p 时 GCC 的输出:

In function ‘int main()’:
error: use of deleted function ‘main()::<anonymous union>::~<constructor>()’
       vector<int> vec; } s = { "Hello, world"s };
                                                ^

note: ‘main()::<anonymous union>::~<constructor>()’ is implicitly deleted because the default definition would be ill-formed:
      union { string str;
            ^

【讨论】:

  • 感谢您的评论。我已经清理了有问题的代码,将自定义析构函数添加到 union 定义中。 This builds 非常感谢您提供的信息,但这个答案并没有试图解决这个问题:当union 超出范围时会发生什么?
【解决方案3】:

您总是需要手动调用结构中具有非平凡类型的对象的构造函数。

通常你也总是需要明确地构造它们。这里的作业似乎很奇怪。

如果有疑问,您可以随时检查程序集是否调用了析构函数。

此代码的程序集确实调用了basic_string 构造函数,但不调用析构函数。所以这里会有泄漏。

using namespace std;
int main(int argc, char** argv){
    union S { string str;
              vector<int> vec;
              ~S() {} } s = { "Hello, world"s };
}

查看程序集的链接:https://godbolt.org/g/wKu3vf

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2011-11-05
  • 1970-01-01
  • 2012-03-18
  • 2016-10-12
  • 2016-01-29
  • 2018-05-13
  • 2011-01-16
  • 1970-01-01
相关资源
最近更新 更多