Can a trivial type class be copied when not all its members are initialized?
Asked Answered
C

1

1

(I just realized I first need to solve a much more basic issue with copying unions: When a union object is copied, is a member subobject created?. Please see that other question first.)

The implicitly generated copy operations (constructor and assignment) of a class perform member by member copy (initialization or assignment). (For a trivial type these are the same.)

So a class with some members not initialized cannot be copied, as accessing uninitialized objects is illegal.

struct C {
  int m1, m2;
};

void f() {
  C c1, c2;

  c1.m1 = 1;
  c2 = c1; // not initialized
}

But a union can always be copied, even if it contains class members, some of which aren't initialized (because... by definition not two members of a unions are initialized).

Does that mean that copying a union of a class with uninitialized members is legal:

union U {
  C m;
};

void g() {
  U u1, u2;
  u1.m.m1 = 1;
  u2 = u1;
}

and if so, can classes be copied by casting to such union?

void f2() {
  C c1, c2;

  c1.m1 = 1;
  (U&)c2 = (U&)c1; // not initialized?
}
Cofferdam answered 8/12, 2019 at 2:6 Comment(24)
A union doesn't have multiple class members in terms of memory. It only has the single class member that is active and then some excess space to accomodate the largest union member. Equivilent to a single member with padding.Osier
@DavidLedger Yes so does copying of a union (of classes) works?Cofferdam
@walnut Casting a class object to a union type, in order to copy the class w/o calling a member function: (union U&)c1 = (union U&)c2; in order to do c1 = c2; avoiding calling operator= when some members aren't initialized.Cofferdam
Well since the active member is not stored anywhere (only in your program logic) I think the propper approach would be to copy the member as if it weren't in a union. union foo foo1, foo2; foo1.member = foo2.member;Osier
For POD unions you can just copy them with memcpy.Osier
@DavidLedger The issue is that an uninitialized variable cannot be copied.Cofferdam
They are not copied, only one member is copyied the active member (which is manually accessed).Osier
If you want a safer version, you can used std::varient, its basically union but it handles the fiddly bits with active members and inactive member undefined behaviour.Osier
@DavidLedger I don't want a safer union, I want a safer class.Cofferdam
Could you add an example?Osier
@DavidLedger I simply want to know whether a class object can be copied via a union. I don't care about unions only classes.Cofferdam
@DavidLedger "only one member is copyied the active member (which is manually accessed)." When copying a union object, how would the compiler know which member to copy?Cofferdam
There are certain restrictions on unions that have non-POD union members. If you attempt to compile such a union, you will find that the union's default copy constructor is deleted. C++ will not let you do this. C++17 effectively replaces unions with type-safe std::variant, with well-defined semantics in this area.Elizebethelizondo
@curiousguy: When you say you "want a safer class" you beg the question: is class unsafe? You say copying a class is illegal if some members are uninitialized, but I think this remarkable claim requires more proof. Admittedly the standard has failed to fully account for this scenario, but I don't think it really says there's UB here.Contemporaneous
@JohnZwinck [decl.init]/12 says that copying an indeterminate value is UB except for specific cases.Actinopod
@SamVarshavchik Yes that question is only about those classes with compiler generated copy operations.Cofferdam
@JohnZwinck Yes my understanding is that copy a scalar member that isn't uninitialized is a big no-no and so is copying a class with such member via the compiler generator copy ctor that performs member by member copy (or with implicitly defined copy assignment).Cofferdam
@Cofferdam I am not sure whether memcpy of the class directly would be allowed. I think that question comes down to essentially the same as yours, because the implicitly-defined union copy assignment is defined to copy the object representation, effectively doing the same as memcpy.Actinopod
It would be fine for POD, Not fine for non-pod because the compiler doesn't nessarily know which member is the active member.Osier
@JohnZwinck See also CWG issue 2264.Actinopod
@walnut: The standard says it's UB "If an indeterminate value is produced by an evaluation." I'm not sure that invoking a default copy constructor does "produce by an evaluation" the members being copied.Contemporaneous
@JohnZwinck [class.copy.ctor]/14 says that the members will be direct-initialized from the corresponding members of the copied-from instance, i.e. it is equivalent to m2(other.m2) in the initializer-list. Would you not consider this to require evaluation of other.m2 to the indeterminate value?Actinopod
@walnut: What if the structure consists of nothing but arrays of unsigned char?Gulch
@Gulch Then there is no UB and no problem, because the exception in [decl.init]/12.3 applies.Actinopod
S
2

Yes, you can copy the union with the uninitialized (indirect) member—via the default copy/move assignment operator that is defined to copy the object representation. (You could also write your own operator that used std::memcpy. It is obviously a wording defect if the defaulted operator doesn’t establish the correct active member, and std::memcpy should do so as well.)

However, you cannot use cast-to-union as a safer copy for normal class objects; the call to the assignment operator has undefined behavior per a special rule needed because it doesn’t involve an access to any (scalar) object. Your own std::memcpy-based implementation would be fine, even via reinterpret_cast(!), but that’s not newsworthy—reading/copying indeterminate values via unsigned char (and perhaps char) and std::byte is always allowed).

Sweated answered 8/12, 2019 at 6:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.