不受限制的联合需要放置new和构造函数定义吗?

Pra*_*tic 8 c++ unions c++11

我看到的无限制工会的例子似乎总是在构建时使用新的位置.维基百科有关C++ 11特性的文章在union的构造函数中使用了placement new.

https://en.wikipedia.org/wiki/C%2B%2B11#Unrestricted_unions

#include <new> // Required for placement 'new'.

struct Point {
    Point() {}
    Point(int x, int y): x_(x), y_(y) {} 
    int x_, y_;
};

union U {
    int z;
    double w;
    Point p; // Illegal in C++03; legal in C++11.
    U() {new(&p) Point();} // Due to the Point member, a constructor definition is now required.
};
Run Code Online (Sandbox Code Playgroud)

是否有必要在这里使用新的位置?例如,这段代码在没有警告的情况下使用gcc进行编译,而valgrind在使用union来保存字符串时没有显示内存泄漏:

struct HasUnresUnion
{
    enum { Int, String } tag;

    HasUnresUnion(int i)
      : tag(Int),
        as_int(i)
    {}

    HasUnresUnion(std::string str)
      : tag(String),
        as_str(std::move(str))
    {}

    ~HasUnresUnion()
    {
        using std::string;
        if (tag == String)
            as_str.~string();
    }

    union
    {
        int as_int;
        std::string as_str;
    };
};
Run Code Online (Sandbox Code Playgroud)

这似乎没有任何歧义,所以我不明白为什么标准会取缔这个.这是合法代码吗?当联合未初始化(而不是被分配)时,是否需要新的位置?是否需要联盟中的构造函数?我确实看到没有自己的构造函数的无限制工会,但维基百科明确表示它是必需的.

Fil*_*efp 9

介绍

你展示的片段非常安全; 在初始化类似联合的类时,法律允许您初始化一个非静态数据成员.


维基百科文章有其中一个例子以展示位置新,因为它们是在其中可以直接初始化为某个部件后点写入一个部件被使用.

union A {
   A () { new (&s1) std::string ("hello world"); }
  ~A () { s1.~basic_string<char> (); }
  int         n1;
  std::string s1;
};
Run Code Online (Sandbox Code Playgroud)

然而,前一个片段在语义上等同于以下内容,其中我们明确声明A::s1在构造时应该初始化A.

union A {
   A () : s1 ("hello world") { }
  ~A () { s1.~basic_string<char> (); }
  int         n1;
  std::string s1;
};
Run Code Online (Sandbox Code Playgroud)

在你的代码片段中,你的类中有一个匿名联合(这使你的类成为一个类似联合的类),这意味着除非你在初始化类时初始化其中一个成员union- 你必须使用placement-new来初始化它们晚些时候.


标准说什么?

9.5/1 -- Unions -- [class.union]p1
Run Code Online (Sandbox Code Playgroud)

在并集中,至多一个非静态数据成员可以在任何时间处于活动状态,也就是说,任何时候最多一个非静态数据成员的值都可以存储在并集中.

3.8/1 -- Object lifetime -- [basic.life]p1
Run Code Online (Sandbox Code Playgroud)

[...]

类型对象的生命周期T从以下开始:

  • 获得具有适当对齐和类型大小的存储T,并且
  • 如果对象具有非平凡的初始化,则其初始化完成.

类型对象的生命周期在以下情况T结束:

  • 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用将启动,或者
  • 对象占用的存储器被重用或释放.