【问题标题】:Destructor of union member seems to be called automatically工会成员的析构函数似乎被自动调用
【发布时间】:2020-06-24 12:45:58
【问题描述】:

我正在尝试实现标记联合。

我的理解是,在 C++ 联合中,非静态成员的非平凡(即非空)析构函数从不调用,因此我们必须自己调用它们。我就是这样做的:

#include <iostream>

class C {
public:
  C() {
    std::cout << "C Ctor" << std::endl;
  }
  ~C() {
    std::cout << "C Dtor" << std::endl;
  }
};

class B {
public:
  B() {
    std::cout << "B Ctor" << std::endl;
  }
  ~B() {
    std::cout << "B Dtor" << std::endl;
  }
};

struct S {
  int type;

  union U {
    C c;
    B b;

    U() {

    }

    ~U() {}
  } u;

  S(int type) : type(type) {
    if (type == 0) {
      u.c = C();
    } else {
      u.b = B();
    }
  }

  ~S() {
    if (type == 0) {
      u.c.~C();
    } else {
      u.b.~B();
    }
  }
};

int main() {
  S s(0);
  return 0;
}

但是,输出是:

C Ctor
C Dtor
C Dtor

意思是,C 析构函数被调用了两次,而不是一次。

发生了什么事?如果您发现我的标记联合实施存在其他问题,请指出。

【问题讨论】:

  • 仅供参考:std::endl 通常是不必要的。 "\n" 具有同样的便携性,不会尝试在每一行刷新输出。
  • 通常你会使用一个匿名联合来实现一个标记联合,你可以在标准中阅读这些内容和/或查看std::variant的实现。跨度>

标签: c++ union destructor


【解决方案1】:

S(int type) : type(type) {
    if (type == 0) {
      u.c = C();
    } else {
      u.b = B();
    }
  }
  

由于您在构造函数的主体中,u.c = C(); 不是初始化而是分配。这意味着您会看到为C() 调用的构造函数,然后在表达式的末尾调用第一个析构函数调用以销毁该临时函数。我们可以通过添加来看到这一点

C& operator=(const C&) { std::cout << "operator=(const C&)\n"; return *this; }

C 将输出更改为

C Ctor
operator=(const C&)
C Dtor
C Dtor

然后第二个析构函数调用是当s 超出 main 的范围并运行其析构函数时。


请注意,代码具有未定义的行为。联合不会激活构造函数中的成员,用户提供的构造函数是你写的,所以当你这样做时

u.c = C();

您正在分配一个尚未激活的对象。您不能修改不存在的对象。

【讨论】:

  • 我明白了。所以析构函数为临时调用一次,只为成员调用一次。正确的?如果是这样 - 你会如何建议改变这个?我在正文而不是初始化列表中实例化C 的原因是因为我需要在type 上进行分支...我想我可以在初始化列表中做三元运算符,但这常见吗?
  • @AvivCohn 使用展示位置new,例如S(int type) : type(type) { if (type == 0) { new (&amp;u.c) C(); } else { new (&amp;u.b) B(); } }。这会给你C Ctor C Dtor 作为输出。
  • @NathanOliver: 你也可以使用Compiler Explorer 之类的工具来查看代码的反汇编,看看在哪里调用了解构函数;非常适合这样的小例子。
  • @AvivCohn 您的示例不起作用的原因是因为它具有未定义的行为,因为它调用了非活动联合成员的赋值运算符,这是非平凡可构造的。 Nathan 的建议(在 cmets 中)没有做到这一点,并且行为定义明确。
  • @AvivCohn 据我所知,是的,激活非活动非平凡工会成员的唯一方法是安置新。
【解决方案2】:

在您的构造函数中创建 C 的临时实例:

u.c = C();

复制然后销毁。所以,前两行输出属于这个实例。 最后一行是你的 ~S() 调用的结果。

除此之外,从 C++17 开始,您可以使用标准强大的标记联合实现:https://en.cppreference.com/w/cpp/utility/variant

【讨论】:

    猜你喜欢
    • 2017-02-27
    • 2011-05-31
    • 2011-11-05
    • 2012-03-18
    • 1970-01-01
    • 2021-07-13
    • 2016-01-29
    • 2018-05-13
    • 2011-01-16
    相关资源
    最近更新 更多