使用 std::launder 从指向非活动工会成员的指针获取指向活动工会成员的指针?
Using std::launder to get a pointer to an active union member from a pointer to an inactive union member?
考虑这个并集:
union A{
int a;
struct{
int b;
} c;
};
c
和a
不是布局兼容的类型,因此无法通过a
读取b
的值:
A x;
x.c.b=10;
x.a+x.a; //undefined behaviour (UB)
对于试验 1 和试验 2,请参阅此问题
试用 3
现在让我们将std::launder
用于它似乎不打算做的事情:
A x;
x.a=10;
auto p = &x.a; //(1)
x.c.b=12; //(2)
p = std::launder(p); //(2')
*p+*p; //(3) UB?
std::launder
能改变什么吗?根据[ptr.launder]:
template <class T> constexpr T* launder(T* p) noexcept;
要求:
p
表示内存中字节的地址A。在其生存期内且类型类似于T
的对象X位于地址A处。可通过结果访问的所有存储字节都可以通过p
访问(见下文)。返回:指向X的
T *
类型的值。备注: 只要核心常量表达式的参数值可用于核心常量表达式,就可以在核心常量表达式中使用此函数的调用。可以通过指向对象 Y 的指针值(如果对象 Y 在 Y 占用的存储内)、指针可与 Y 相互转换的对象或紧紧封闭的数组对象(如果 Y 是数组元素)来访问存储字节。如果 T 是函数类型或 cv void,则程序格式不正确。
粗体句子强调了一个困扰我的东西。如果p
是无效的指针值,如何访问任何存储字节?另一方面,使用这样的读数std::launder
是无法使用的。
否则,p
在 (2) 处的值是否可以是一个指针值,表示存储区域,如 [basic.life] 中的"注释"中所述:
如果不满足这些条件,则可以通过调用
std::launder
([support.dynamic]) 从表示其存储地址的指针获取指向新对象的指针。
在注释中明确允许。
basic.life
包含以下规则,使std::launder
变得不必要:
之前,在原始对象占用的存储位置创建一个新对象、指向原始对象的指针、引用原始对象的引用或原始对象的名称,并且, 一旦新对象的生存期开始,可用于操作新对象,如果:
- 新对象的存储完全覆盖原始对象占用的存储位置,并且
- 新对象与原始对象的类型相同(忽略顶级 CV 限定符),并且
原始对象的类型不是常量限定的- ,如果是类类型,则不包含任何类型为常量限定或引用类型的非静态数据成员,并且
- 原始对象和新对象都不是可能重叠的子对象。
[ 注意:如果不满足这些条件,则可以通过调用
std::launder
从表示其存储地址的指针获取指向新对象的指针。 — 尾注 ]
这种"在原始对象占用的存储位置创建新对象"的情况在这里显然适用,因为:
已创建一个对象...当隐式更改工会的活跃成员时...
满足所有项目符号条件,因为"潜在重叠的子对象"是指基类子对象,而联合成员不是。 (在您链接到的版本中,该项目符号直接提到了基类子对象。
然而,即使这种解释要改变,反对工会,说明也特别提到std::launder
绕过了这一限制。
请注意,旧版本的标准从此规则中排除了子对象...但该说明清楚地表明,std::launder
也会绕过这个问题。
- 从成员指针到整个结构/类的强制转换
- OOP 中的单成员指针
- 使用结构成员指针在C++中填充结构
- 聚合初始化,将成员指针设置为同一结构成员
- 空指针常量 (nullptr)、空指针值和空成员指针值之间有什么区别?
- 共享 C++ 的数据成员指针
- 如何声明指向成员内容的成员指针m_pmd/m_pmf并访问它们?
- 从类C++外部调用指向成员方法的成员指针
- 结构成员指针是否自动初始化为零?
- 如何删除类内类类型的类成员指针
- 是否打印指向已定义 int 的成员指针
- 指向常量的成员指针
- 通过此指针访问时的成员差异和自身的成员指针(简单的 QT 示例问题)
- 类数据成员指针的非类型模板参数包无法使用 gcc 编译
- 为什么我不能在同一行中定义两个相同类型的类的成员指针
- 将类成员指针传递给 Lambda 捕获列表 c++11
- 在单一实例类中将成员指针设置为 null 的正确方法是什么
- 无法删除在destructor中的成员指针的课程
- 删除对象而不调用成员指针的析构函数
- 当派生类的基类具有成员指针时,对其进行深层复制