如P0532R0中所述,必须使用以下用例std::launder
来避免未定义的行为(UB):
struct X{
const int i;
x(int i):i{i}{}
};
unsigned char buff[64];
auto p = new(buff) X(33);
p->~X();
new(buff) X(42);
p = std::launder(p);
assert(p->i==42);
但是在缓冲区中有多个对象的情况下会发生什么(这正是在向量中推送2 X
时会发生的情况,清除向量然后再推送两个{{1} }):
X
最后一个断言是正确的,还是unsigned char buff[64];
auto p0 = new(buff) X(33);
auto p1 = new(p0+1) X(34);
p1->~X();
p0->~X();
new(buff) X(42);
new(p0+1) X(43);
p0 = std::launder(p0);
assert(p0->i==42);
assert(p0[1].i==43);//???
仍然调用UB?
答案 0 :(得分:5)
您的代码调用UB,但不是launder
原因。这是因为p0[1].i
本身就是UB。
是的,真的([Expr.Add] / 4):
当应用于指针时,当向指针添加或从指针中减去具有整数类型的表达式时,结果具有指针操作数的类型。如果表达式P指向具有n个元素的数组对象x的元素x [i],则表达式P + J和J + P(其中J具有值j)指向(可能是假设的)元素x [i + j]如果0≤i+j≤n;否则,行为未定义。同样,如果0≤i-j≤n,则表达式P-J指向(可能是假设的)元素x [i-j];否则,行为未定义。
为此目的,不是数组元素的对象被认为属于单元素数组;见8.3.1。为了这个目的,超过n个元素的数组x的最后一个元素的指针被认为等同于指向假设元素x [n]的指针。见6.9.2。
[]
意味着执行指针运算。在C ++对象模型中,指针算法只能用于指向所指向类型的数组中元素的指针。您始终可以将对象视为长度为1的数组,因此您可以获得指向单个对象的“一个结束”的指针。因此,p0 + 1
有效。
not 有效的是访问通过p0 + 1
获得的指针存储在该地址的对象。也就是说,p0[1].i
是未定义的行为。这就像之前的之前的UB 一样。
现在,让我们看看另一种可能性:
X x[2];
x[1].~X(); //Destroy this object.
new(x + 1) X; //Construct a new one.
所以让我们问一些问题:
x[1]
UB?我会说......不,这不是UB。为什么?因为x[1]
不是:
指向原始对象的指针,引用原始对象的引用或原始对象的名称
x
指向数组和该数组的第一个元素,而不是第二个元素。因此,它没有指向原始对象。它不是参考,也不是该对象的名称。
因此,它不符合[basic.life] / 8规定的限制。所以x[1]
应该指向新构造的对象。
鉴于此,您根本不需要launder
。
因此,如果您以合法的方式执行此操作,那么此处不需要launder
。
答案 1 :(得分:2)
首先需要std::launder
的原因是[basic.life]
如果在对象的生命周期结束之后并且在重用或释放对象占用的存储之前,则在原始对象占用的存储位置创建新对象,指向原始对象的指针,引用原始对象的引用,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,就可以用来操纵新对象,如果:[... ]
- 原始对象的类型不是const限定的,如果是类类型,则不包含任何类型为const限定或引用类型的非静态数据成员,并且[...]
因此,如果没有std::launder
,p
,原始指针将不会指向新构造的对象。
如果不满足这些条件,可以通过调用
从表示其存储地址的指针获取指向新对象的指针。std::launder
std::launder
执行what it does的原因。
来自[ptr.launder],恰当地命名为指针优化障碍
如果在由相同类型的现有对象占用的存储中创建新对象,则可以使用指向原始对象的指针来引用新对象,除非该类型包含const或引用成员;在后一种情况下,此函数可用于获取指向新对象的可用指针。
除非清洗,否则原始指针不能用于引用新构造的对象。
从我所看到的,它可以被解释为两种方式(可能完全错误)。
由于std::launder
是指针优化障碍,我个人认为第一个是真的。