将非活动std::unique_ptr数据成员交换为联合
swap non-active std::unique_ptr data members for 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
{
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
s合法吗?是否允许交换非活动std::unique_ptr
的U
类联合的替代数据成员?
由于std::unique_ptr< X >
的简单性质,它似乎是允许的:它只是X *
的包装器,对于任何A
和B
,我确信static_assert((sizeof(A *) == sizeof(B *)) && (alignof(A *) == alignof(B *)));
持有和指针排列对于所有类型都是相同的(除了指向数据成员和类的成员函数的指针)。这是真的吗?
示例代码工作良好。但如果我们阅读标准,很可能存在UB。
from§ 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]……指针类型的值表示形式是由实现定义的。指向layout-compatible的cv-qualified和cv-unqualified版本(3.9.3)的指针类型应具有相同的值表示和对齐要求…
因此,我们可以相信存储在std::unique_ptr
中的指针的值在联合体的另一个成员中是可表示的。
这里没有未定义行为
IMHO,你有正式的未定义行为,因为你总是访问联合的a部分,即使最后写的是b。
当然可以,因为除了管理之外,unique_ptr只包含一个原始指针和一个存储的delete。指向任何类型的指针都具有相同的表示,除了对齐问题,将指向X的指针转换为指向Y的指针并返回是安全的。所以在低层 if交换原始指针是安全的。它可能更依赖于实现,但我认为交换存储的删除器也是安全的,因为实际存储的通常是一个地址。无论如何,对于struct A
和struct B
类型,析构函数都是无操作的。
唯一可能导致代码失败的情况是,编译器强制执行了这样的规则,即除了公共初始子序列之外,只能访问联合的最后一个写入成员。对于当前的编译器,我很确定没有强制执行,所以它应该可以工作。
但是在我曾经问过的关于另一种可能的UB情况的问题中,Hans Passant给出了能够检测缓冲区溢出的高级编译器的研究工作的链接。我真的认为同样的技术可以用来强制访问联合成员的规则,这样这样的编译器就可以在运行时对你的代码引发异常。
TL/DR:这段代码应该在所有当前已知的编译器上工作,但由于它不是严格的标准一致性,未来的编译器可能会被它困住。因此,我称之为正式未定义行为。
- 用于访问容器<T>数据成员的正确 API
- 静态数据成员的问题-修复链接错误会导致编译器错误
- 数据成员SFINAE的C++17测试:gcc vs clang
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 如何在c++中定义以struct为数据成员的类中的构造函数
- 静态数据成员模板专用化的实例化点在哪里
- int数据类型的指针指向的是什么,如果是一个类的私有数据成员,我们创建了该类的两个对象?
- 使用指针访问数组中的对象数据成员
- 友元函数无法访问私有数据成员 (c++)
- 我可以在 C++ 中将数据成员/变量从其定义之外添加到结构中吗?
- 为什么将一个结构的引用设置为等于另一个结构只会更改一个数据成员?
- 将私有数据成员添加到野牛生成的类中
- 输入数据成员未按要求工作
- 二维矢量数据成员
- 在类 A 中创建类型为 B 类的向量 - 访问数据 [C++] [成员在两个类中都是私有的]
- 调用在 HXX 文件中声明的静态数据成员
- 是否可以根据其数据成员的类型确定类型的大小
- 排序链表-移动节点还是交换数据成员
- 当数组是类的数据成员时,如何使用指针交换数组
- 将非活动std::unique_ptr数据成员交换为联合