C++ 交换非活动std::union的唯一\u ptr数据成员
给定一个联盟:C++ 交换非活动std::union的唯一\u ptr数据成员,c++,c++14,smart-pointers,unique-ptr,discriminated-union,C++,C++14,Smart Pointers,Unique Ptr,Discriminated Union,给定一个联盟: #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 {
#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;
}
#包括
#包括
#包括
#包括
#包括
#包括
结构A{int A;};
结构B{int B;};
模板
结构
{
标准:尺寸标签;
std::unique_ptrX;
};
联合大学
{
SA;
SB;
U(ax):A{0,std::使_唯一(x)}{;}
U(bx):B{1,std::使_唯一(x)}{;}
std::size\u t tag(){return a.tag;}
~U()
{
开关(标记()){
案例0:{
a、 ~S();
打破
}
案例1:{
b、 ~S();
打破
}
默认值:断言(false);
}
}
无效的
掉期(U&U)无例外
{
a、 互换(u.a.x);
标准::交换(a.tag,u.a.tag);
}
};
静态断言(std::is_标准布局{});
int
main()
{
U a{a{0};
U b{b{~0};
断言((a.tag()==0)&&(a.a.x->a==0));
断言((b.tag()==1)和&(b.b.x->b==0));
a、 掉期(b);
断言((a.tag()==1)&&(a.b.x->b==0));
断言((b.tag()==0)和&(b.a.x->a==0));
返回退出成功;
}
U::tag()
函数是正确的,因为它允许检查类U
联合中替代数据成员的公共初始子序列
U::swap()
有效,但是std::unique\U ptr
s合法吗?是否允许交换非活动的std::unique_ptr
sU
类联合的替代数据成员
由于std::unique\u ptr
的简单性质,它似乎是允许的:它只是X*
上的一个包装,对于任何a
和B
我确信静态断言((sizeof(a*)==sizeof(B*)&(alignof(a*)==alignof(B*))代码>保持和指针排列对于所有类型都是相同的(除了指向类的数据成员和成员函数的指针)。这是真的吗
示例代码运行良好。但是,如果我们阅读标准,很可能会出现UB。嗯,您有正式的未定义行为,因为您总是访问联合体的a部分,即使最后一次编写的是b
当然,它可以工作,因为除了它的管理之外,一个唯一的ptr只包含一个原始指针和一个存储的deleter。指向任何类型的指针都有相同的表示形式,除了对齐问题,将指向X的指针转换为指向Y的指针并返回是安全的。所以在低级别,交换原始指针是安全的。它可能更依赖于实现,但我认为交换存储的删除器也是安全的,因为实际存储的通常是地址。无论如何,对于类型structA
和structB
,析构函数都是不可操作的
唯一可能导致代码失败的是,如果编译器强制执行只有最后一个写入的联合成员才能被访问的规则,除了公共初始子序列。对于当前的编译器,我敢肯定没有人强制执行这一点,因此它应该可以工作
但在我曾经问过的一个问题中,Hans Passant对能够检测缓冲区溢出的高级编译器进行了研究。我真的认为可以使用相同的技术来强制执行对联盟成员的访问规则,这样编译器就可以在运行时用您的代码引发异常
TL/DR:这段代码应该适用于所有当前已知的编译器,但由于它不是严格符合标准的,未来的编译器可能会使用它。因此,我将这种形式上的未定义行为称为
§9.5工会
特别是关于标准布局类型的注释:
。。。一项特别保证
是为了简化联合体的使用:如果一个标准布局联合体包含多个标准布局联合体
共享公共初始序列(9.2)的结构,如果是此标准布局联合类型的对象
包含一个标准布局结构,允许检查任何
标准布局结构成员
因此,允许对任一联合成员使用公共初始序列
在您的情况下,常见的初始序列肯定是std::size\u t tag
。然后我们需要知道所有T
的std::unique_ptr
是否相同,因此它也可以被视为公共初始序列的一部分:
§20.8.1类别模板唯一性\u ptr
[1] 唯一指针是拥有另一个对象并通过指针管理该另一个对象的对象。
更准确地说,唯一指针是一个对象u
,它存储指向第二个对象p
的指针
是的。但是我们怎么知道所有指针都是相同的呢?对你来说:
§3.9.2化合物类型
[ 3 ] ... 指针类型的值表示
是否定义了实现。指向布局兼容的cv合格和cv不合格版本(3.9.3)的指针
类型应具有相同的值表示和对齐要求
因此,我们可以依赖存储在std::unique_ptr
中的指针的值,该值可以在联合体的其他成员中表示
因此,这里没有未定义的行为。您确定std::unique\u ptr
是标准布局吗?您可以添加一个静态断言吗?@KerrekSB请参阅编辑(clang 3.7 eat it)。@Orient您在tag()
中确实有未定义的行为。@CoffeeandCode具有某些背景。常用的初始顺序规则仅适用于标准布局联合U
不保证是标准布局,因为不要求是标准布局。此外,即使对unique_ptr
表示的简单描述成立,指向不同任意对象类型的指针也不兼容布局,这是确定公共初始序列的要求。