Unlimited alliance in practice

I have some questions about unlimited unions and their application in practice. Suppose I have the following code:

struct MyStruct { MyStruct(const std::vector<int>& a) : array(a), type(ARRAY) {} MyStruct(bool b) : boolean(b), type(BOOL) {} MyStruct(const MyStruct& ms) : type(ms.type) { if (type == ARRAY) new (&array) std::vector<int>(ms.array); else boolean = ms.boolean; } MyStruct& operator=(const MyStruct& ms) { if (&ms != this) { if (type == ARRAY) array.~vector<int>(); // EDIT(2) if (ms.type == ARRAY) new (&array) std::vector<int>(ms.array); else boolean = ms.boolean; type = ms.type; } return *this; } ~MyStruct() { if (type == ARRAY) array.~vector<int>(); } union { std::vector<int> array; bool boolean; }; enum {ARRAY, BOOL} type; }; 

EDIT:

  • Yes, it compiles
  • "Members declared within anonymous associations are actually members of the containing class and can be initialized in the constructor of the containing class." ( anonymous C ++ 11 union with non-trivial members )
  • Adding explicit destructors as suggested leads to SIGSEV with g ++ 4.8 / clang 4.2
+4
source share
2 answers
  • Code error: modify array.clear(); on array.~vector<int>();

Explanation: operator= uses a new placement over an object that has not been destroyed, which can do anything, but you can practically expect the dynamic memory leak used by the previous array ( clear() doesn’t free memory / change capacity, it just destroys the elements and size changes).

From 9.5 / 2:

If any non-static member of the union data has a non-trivial default value, constructor (12.1), copy constructor (12.8), move constructor (12.8), copy assignment operator (12.8), move (12.8) or destructor (12.4), the corresponding member function the union must be the user or it will be implicitly deleted (8.4.3) for the association.

So, the vector constructor, destructor, etc. they never start on their own: you must invoke them explicitly whenever you want.

An example is given in 9.5 / 3:

Consider the following union:

 union U { int i; float f; std::string s; }; 

Since std :: string (21.3) declares non-trivial versions of all special member functions, U will have an implicitly deleted default constructor, copy / move constructor, copy / move operator and destructor. To use U, some or all of these member functions must be provided by the user.

This last bit is "To use U, some or all of these member functions must be provided by the user." - it seems that it is supposed that U should coordinate its own indefinite value-semantic behavior, but in your case it means that the struct does this, so you do not need to define any of these union member functions.

2: we must call the array destructor whenever the value of the array is replaced with a boolean value. If in operator= new value of the array instead of the location is assigned the location new ed, then the old array should also have its own destructor, but using operator= will be more efficient if the existing memory is enough for all elements to be copied. In principle, you must comply with structures and demolitions. UPDATE: The sample code contains an error as per your comment below.

3: Why is a new placement required instead of doing something like "array = ms.array"?

array = ms.array calls std::vector<int>::operator= , which always assumes that the this pointer refers to an already properly constructed object. Inside this object, you can expect that there will be a pointer that will either be NULL, or refer to some internal buffer with a short string, or refer to a bunch. If your object has not been destroyed, then operator= can call the function to free memory on a dummy pointer. The new placement says "ignore the current contents of the memory that this object will occupy, and build a new object with valid members from scratch.

+2
source

The union does not declare a default constructor, copy constructor, copy assignment operator, or destructor.

If std::string declares at least one non-trivial version of a special member function (in this case), all deduced from them are all implicitly deleted, and you must declare (and define) them (... if they're used, which has a place).

To some extent, this code is incorrect and should not be successfully compiled (this is almost-to-letter, identical to the example in 9.5 par. 3 of the standard, except for std::string , not std::vector ).
(Not applicable for anonymous merging, as indicated correctly)

About question (2): To safely switch the union, it is necessary, yes. The standard clearly says that at 9.5 pairs. 4 [Note].
It also makes sense if you think about it. No more than one data member can be active in union at any time, and they are not magically built / destroyed by default, which means that you need to properly construct / destroy things. It does not make sense (or even defined) to use union as something else (not that you still could not do this, but it is undefined).
An object is not a pointer, and you do not know if it was allocated on the heap (even if it is allocated on the heap, and then inside another object, so it cannot be resolved yet). How do you destroy an object if you cannot call delete ? How do you select an object - perhaps several times - without leakage if you cannot delete it? This does not leave many options. Because [Note] makes sense.

+1
source

Source: https://habr.com/ru/post/1481876/


All Articles