Do unrestricted unions require placement new and a constructor definition?
Asked Answered
T

2

10

The examples I've seen of unrestricted unions always seem to use placement new when constructing. The Wikipedia article for C++11 features uses placement new in the constructor of a union.

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.
};

Is it necessary to use placement new here? For example, this piece of code compiles without warnings with gcc and valgrind shows no memory leaks when the union is used to hold a string:

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;
    };
};

It doesn't seem like there's any ambiguity here so I don't see why the standard would outlaw this. Is this legal code? Is placement new necessary when the union is uninitialized (rather than being assigned to)? Is the constructor in the union required? I've definitely seen unrestricted unions without their own constructors but Wikipedia explicitly states it's required.

Tableland answered 10/10, 2015 at 20:36 Comment(5)
Just a somewhat related note: A constructor initializer list could be used as well.Suite
@JoachimPileborg Isn't the OP doing just that? (Or, at least, asking whether it is allowed.)Multilateral
@JoachimPileborg I don't understand the comment. :( The code uses a member initializer list.Tableland
You structure uses it, but you could use it for the union constructor as well. No need for placement new. I don't know if it's really allowed by the specification (don't have it "on me" right now), but I don't see why not. A constructor is a constructor is a constructor.Suite
@JoachimPileborg Ah, gotcha. I just feel weird about giving my unions member functions but even if the union had a ctor the question would be the same.Tableland
I
12

Introduction

The snippet you have shown is perfectly safe; you are legally allowed to initialize one non-static data-member when initializing your union-like class.


Example

The wikipedia article has an example where placement-new is used because they are writing to a member after the point in which it is possible to directly initialize a certain member.

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

The previous snippet is however semantically equivalent to the following, where we explicitly state that A::s1 shall be initialized when constructing A.

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

Elaboration

In your snippet you have an anonymous union inside your class (which makes your class a union-like class), which means that unless you initialize one of the members of the union during initialization of the class — you must use placement-new to initialize them at a later time.


What does the Standard say?

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

In a union, at most one of the non-static data members can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time.

3.8/1 -- Object lifetime -- [basic.life]p1

[...]

The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • if the object has non-trivial initialization, its initialization is complete.

The lifetime of an object of type T ends when:

  • if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
  • the storage which the object occupies is reused or released.
Ithaman answered 10/10, 2015 at 20:58 Comment(0)
F
1

No, placement new is not required here. The standard says that in case of unrestricted unions, the field constructors should be called explicitly, otherwise the fields will be uninitialized.

You can call the constructor using traditional way

U(): p() {}

and exotic way

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

The second variant may be useful if it is not possible to construct the field before calling the constructor of U.

Don't also forget about placement destructors in this case.

Focus answered 10/10, 2015 at 20:49 Comment(4)
Does the union itself require a constructor? Is something like the example I gave legal?Tableland
Yes, it requires an explicitly defined constructor if any of the union fields contains non-trivial special member functions. Yes, the code seems legal. But you can be handcutted if you use this code in real projects.Focus
Placement delete? Usually the analogue to placement new is just manually calling the destructor.Shaddock
Yes, I meant destructorsFocus

© 2022 - 2024 — McMap. All rights reserved.