包含包装类型和类型本身的联合是否有任何保证?
Are there any guarantees for unions that contain a wrapped type and the type itself?
我可以把一个 T
和一个包裹的 T
放在一个 union
中并随意检查它们吗?
union Example {
T value;
struct Wrapped {
T wrapped;
} wrapper;
};
// for simplicity T = int
Example ex;
ex.value = 12;
cout << ex.wrapper.wrapped; // ?
C++11 标准只保证公共初始序列的保存检查,但 value
不是 struct
。我猜测答案是否,因为 and accessing inactive members is only well-defined on common initial sequences.
当访问一个不是最后写入的成员时,联合行为是未定义的。所以不,你不能依赖这种行为。
它在原理上与使用联合从整数中提取特定字节的想法相同;但有一个额外的风险,即您现在依赖编译器不在您的结构中添加任何填充。有关详细信息,请参阅 Accessing inactive union member and undefined behavior?。
它应该可以工作,因为 Example
和 Wrapped
都是 标准布局 类,并且 C++14 标准有足够的要求保证在这种情况下 value
和 wrapper.wrapped
位于同一地址。 n4296 草案在 9.2 Class 成员 [class.mem] §20:
中说
If a standard-layout class object has any non-static data members, its address is the same as the address
of its first non-static data member.
一张纸条甚至说:
[ Note: There might therefore be unnamed padding within a standard-layout struct
object, but not at its beginning, as necessary to achieve appropriate alignment. —end note ]
这意味着您至少要遵守 3.10 左值和右值 [basic.lval] §10
中的 严格别名规则
If a program attempts to access the stored value of an object through a glvalue of other than one of the
following types the behavior is undefined
— the dynamic type of the object,
...
— an aggregate or union type that includes one of the aforementioned types among its elements or nonstatic
data members (including, recursively, an element or non-static data member of a subaggregate
or contained union),
所以这是完美的定义:
cout << *(&ex.wrapper.wrapped) << endl
因为 &ex.wrapper.wrapped
需要与 &ex.value
相同,并且指向的对象具有正确的类型。
.
但由于标准仅对 公共子序列 是显式的。所以我的理解是 cout << ex.wrapper.wrapped << endl
调用未定义的行为,因为 1.3.24 [defns.undefined] 中的注释
未定义的行为
说(强调我的):
Undefined behavior may be expected when this International Standard omits any explicit definition of
behavior...
TL/DR:我敢打赌,即使不是所有常见实现也会接受它,但由于 1.3.24 [defns.undefined] 的注释,我永远不会在生产代码,但会使用 *(&ex.wrapper.wrapped)
代替。
在 C++17 的最新草案 n4659 中,相关概念是 相互转换([basic.compound] §4)。
我认为这是未定义的行为。
[class.mem] 给我们:
The common initial sequence of two standard-layout struct types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that corresponding entities have layout-compatible types and either neither entity is a bit-field or both are bit-fields with the same width. [...]
In a standard-layout union with an active member of struct type T1
, it is permitted to read a non-static data member m
of another union member of struct type T2
provided m is part of the common initial sequence of T1
and T2
; the behavior is as if the corresponding member of T1
were nominated.
如果 T
不是标准布局结构类型,这显然是未定义的行为。 (请注意 int
不是标准布局结构类型,因为它根本不是 class 类型)。
但即使对于标准布局结构类型,构成“公共初始序列”的内容也严格基于非静态数据成员。也就是说,T
和 struct { T val; }
没有共同的初始序列 - 根本没有共同的数据成员!
因此,这里:
template <typename T>
union Example {
T value;
struct Wrapped {
T wrapped;
} wrapper;
};
Example<int> ex;
ex.value = 12;
cout << ex.wrapper.wrapped; // (*)
您正在访问一个不活跃的工会会员。那是未定义的。
我可以把一个 T
和一个包裹的 T
放在一个 union
中并随意检查它们吗?
union Example {
T value;
struct Wrapped {
T wrapped;
} wrapper;
};
// for simplicity T = int
Example ex;
ex.value = 12;
cout << ex.wrapper.wrapped; // ?
C++11 标准只保证公共初始序列的保存检查,但 value
不是 struct
。我猜测答案是否,因为
当访问一个不是最后写入的成员时,联合行为是未定义的。所以不,你不能依赖这种行为。
它在原理上与使用联合从整数中提取特定字节的想法相同;但有一个额外的风险,即您现在依赖编译器不在您的结构中添加任何填充。有关详细信息,请参阅 Accessing inactive union member and undefined behavior?。
它应该可以工作,因为 Example
和 Wrapped
都是 标准布局 类,并且 C++14 标准有足够的要求保证在这种情况下 value
和 wrapper.wrapped
位于同一地址。 n4296 草案在 9.2 Class 成员 [class.mem] §20:
If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member.
一张纸条甚至说:
[ Note: There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment. —end note ]
这意味着您至少要遵守 3.10 左值和右值 [basic.lval] §10
中的 严格别名规则If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined
— the dynamic type of the object,
...
— an aggregate or union type that includes one of the aforementioned types among its elements or nonstatic data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
所以这是完美的定义:
cout << *(&ex.wrapper.wrapped) << endl
因为 &ex.wrapper.wrapped
需要与 &ex.value
相同,并且指向的对象具有正确的类型。
.
但由于标准仅对 公共子序列 是显式的。所以我的理解是 cout << ex.wrapper.wrapped << endl
调用未定义的行为,因为 1.3.24 [defns.undefined] 中的注释
未定义的行为
说(强调我的):
Undefined behavior may be expected when this International Standard omits any explicit definition of behavior...
TL/DR:我敢打赌,即使不是所有常见实现也会接受它,但由于 1.3.24 [defns.undefined] 的注释,我永远不会在生产代码,但会使用 *(&ex.wrapper.wrapped)
代替。
在 C++17 的最新草案 n4659 中,相关概念是 相互转换([basic.compound] §4)。
我认为这是未定义的行为。
[class.mem] 给我们:
The common initial sequence of two standard-layout struct types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that corresponding entities have layout-compatible types and either neither entity is a bit-field or both are bit-fields with the same width. [...]
In a standard-layout union with an active member of struct type
T1
, it is permitted to read a non-static data memberm
of another union member of struct typeT2
provided m is part of the common initial sequence ofT1
andT2
; the behavior is as if the corresponding member ofT1
were nominated.
如果 T
不是标准布局结构类型,这显然是未定义的行为。 (请注意 int
不是标准布局结构类型,因为它根本不是 class 类型)。
但即使对于标准布局结构类型,构成“公共初始序列”的内容也严格基于非静态数据成员。也就是说,T
和 struct { T val; }
没有共同的初始序列 - 根本没有共同的数据成员!
因此,这里:
template <typename T>
union Example {
T value;
struct Wrapped {
T wrapped;
} wrapper;
};
Example<int> ex;
ex.value = 12;
cout << ex.wrapper.wrapped; // (*)
您正在访问一个不活跃的工会会员。那是未定义的。