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

时间:2015-10-10 20:36:20

标签: c++ c++11 unions

我看到的无限制工会的例子似乎总是在构建时使用新的位置。维基百科有关C ++ 11特性的文章在union的构造函数中使用了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.
};

是否有必要在这里使用新的位置?例如,这段代码在没有警告的情况下使用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;
    };
};

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

2 个答案:

答案 0 :(得分:9)

简介

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

实施例

维基百科文章中有一个示例,其中使用 placement-new ,因为他们在可以直接初始化某个成员的点之后写入成员。

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

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

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

精化

在您的代码段中,您的班级中有一个匿名联盟(这使您的班级成为类似联盟的班级),这意味着除非您初始化一个 - 您必须使用 placement-new 在以后初始化它们。

标准说什么?

  
union
     
    

在联合中,最多只有一个非静态数据成员可以在任何时候处于活动状态     时间,即至多一个非静态数据成员的值即可     随时存放在工会中。

  
  
9.5/1 -- Unions -- [class.union]p1
     
    

[...]

    类型为3.8/1 -- Object lifetime -- [basic.life]p1的对象的生命周期始于:

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

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

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

答案 1 :(得分:1)

不,此处不需要展示新品。该标准规定,在不受限制的联合的情况下,应明确调用字段构造函数,否则字段将是未初始化的。

您可以使用传统方式调用构造函数

U(): p() {}

和异国情调的方式

U() { new(&p) Point(); }

如果在调用U的构造函数之前无法构造字段,则第二个变体可能很有用。

在这种情况下,不要忘记放置析构函数。