鉴于工会:
#include <iostream>
#include <memory>
#include <type_traits>
#include <vector>
#include <cassert>
#include <cstdlib>
struct A { int a; };
struct B { int b; };
template< typename X >
struct S
{
std::size_t tag;
std::unique_ptr< X > x;
};
union U
{
S< A > a;
S< B > b;
U(A x) : a{0, std::make_unique< A >(x)} { ; }
U(B x) : b{1, std::make_unique< B >(x)} { ; }
std::size_t tag() { return a.tag; }
~U()
{
switch (tag()) {
case 0 : {
a.~S< A >();
break;
}
case 1 : {
b.~S< B >();
break;
}
default : assert(false);
}
}
void
swap(U & u) noexcept
{
a.x.swap(u.a.x);
std::swap(a.tag, u.a.tag);
}
};
static_assert(std::is_standard_layout< U >{});
int
main()
{
U a{A{ 0}};
U b{B{~0}};
assert((a.tag() == 0) && (a.a.x->a == 0));
assert((b.tag() == 1) && (b.b.x->b == ~0));
a.swap(b);
assert((a.tag() == 1) && (a.b.x->b == ~0));
assert((b.tag() == 0) && (b.a.x->a == 0));
return EXIT_SUCCESS;
}
U::tag()
函数是正确的,因为它允许检查U
中的替代数据成员的共同初始子序列 - 如工会。
U::swap()
有效,但std::unique_ptr
是否合法?是否允许交换std::unique_ptr
的非活跃U
替代数据成员 - 就像工会一样?
由于std::unique_ptr< X >
的简单性质,它似乎是可以允许的:它只是X *
的封装,对于任何A
和B
,我确定{{1}对于所有类型,hold和指针排列是相同的(除了指向数据成员和类的成员函数的指针)。这是真的吗?
示例代码工作正常。但如果我们阅读标准,很可能会有UB。
答案 0 :(得分:1)
恕我直言,你有正式的未定义行为,因为你总是访问部分联盟,即使最后写的是b。
当然它可以工作,因为除了它的管理之外,unique_ptr只包含一个原始指针和一个存储的删除器。任何类型的指针都具有相同的表示形式,除了对齐问题之外,将指向X的指针转换为指向Y和Y的指针是安全的。所以在低级别如果可以安全地交换原始指针。它可能更依赖于实现,但我认为交换存储的删除器也是安全的,因为实际存储的通常是地址。无论如何,对于类型struct A
和struct B
,析构函数只是无操作。
唯一可能导致代码失败的是,如果编译器强制执行只能访问联合的最后写入成员的规则,则除了公共初始子序列之外。对于当前的编译器,我很确定没有强制执行,因此它应该可以工作。
但在我曾经问过another possible UB case的一个问题中,Hans Passant给出了link关于能够检测缓冲区溢出的高级编译器的研究工作。我真的认为可以使用相同的技术来强制执行关于访问union成员的规则,因此这些编译器可以在运行时使用您的代码引发异常。
TL / DR:此代码应该适用于所有当前已知的编译器,但由于不严格符合标准,未来的编译器可能会陷入困境。因此,我称之为正式的未定义行为。
答案 1 :(得分:1)
这
§ 9.5 Unions
特别是关于标准布局类型的说明:
......一个特别的保证 是为了简化联合的使用:如果标准布局联合包含几个标准布局 共享公共初始序列(9.2)的结构,以及此标准布局联合类型的对象 包含一个标准布局结构,允许检查任何一个的公共初始序列 标准布局结构成员......
因此,允许将共同的初始序列用于任何一个联盟成员。
在您的情况下,公共初始序列绝对是std::size_t tag
。然后我们需要知道std::unique_ptr<T>
对于所有T
是否相同,因此它也可以被视为公共初始序列的一部分:
§20.8.1班级模板
unique_ptr
[1]唯一指针是拥有另一个对象并通过指针管理该另一个对象的对象。 更确切地说,唯一指针是一个对象u
,它存储指向第二个对象的指针p
...
是的。但我们怎么知道所有指针都代表相同呢?那么,在你的情况下:
§3.9.2化合物类型
[3] ...指针类型的值表示 是实现定义的。指向符合cv标准和cv不合格版本(3.9.3)的布局兼容的指针 类型应具有相同的值表示和对齐要求......
因此,我们可以依赖存储在std::unique_ptr
中的指针的值,该值是在union的另一个成员中可表示的值。
所以不,这里没有未定义的行为。