Basic.life/8告诉我们,我们可以使用对象占用的存储在其生命周期结束后创建一个新对象,并使用其原始名称引用它,除非:
- 原始对象的类型不是const限定的,并且,如果是类类型,则不包含任何类型为const限定或引用类型的非静态数据成员,并且[…]
emphasis mine
但是,就在下面,我们可以看到一张纸条,上面写着:
- 如果不满足这些条件,则可以通过调用
std::launder
从表示其存储地址的指针中获得指向新对象的指针
这解释了std::launder
的用途。我们可以有一个具有const
成员的类类型,并使用placementnew在那里创建一个具有不同内部值的新对象。
让我惊讶的是第一句话的第一部分。它清楚地表明,如果存储是const
(不一定包含const
成员,但整个对象被声明为const
),我们不能按原样使用它来引用新对象,但这可能意味着std::launder
可能会修复它。
但我们将如何创造这样的物体呢?最简单的例子失败了:
const auto x = 5;
new (&x) auto(10);
这是因为我们不能使用const void*
作为放置new的缓冲区。我们可以const_cast
它,但扔掉一个";真";const
ness是未定义的行为。我相信这里也是这样。
如果只是禁止使用const
对象作为放置新缓冲区,我会理解,但如果是这样的话,第一句话中强调的部分的目的是什么?我们是否可以将一个真正的const
对象的存储用于另一个对象?
显然,它所需要的只是查看在我链接的标准部分下方的2个项目。Basic.life/10告诉我们:
在具有静态、线程或自动存储持续时间的const完整对象所占用的存储中,或在此类const对象在其生存期结束前所占用的存储器中创建新对象,会导致未定义的行为。
它附带了一个例子:
struct B {
B();
~B();
};
const B b;
void h() {
b.~B();
new (const_cast<B*>(&b)) const B; // undefined behavior
}
这最终使我得出结论,在由真正的const
对象占用的内存上使用放置-new
是非法的。因此,我认为问题中提到的说明(关于第8点)是错误的——它应该排除有关案件。
常量对象可以有指向其位置的非常量指针。
struct bar{ int x=5; };
struct foo{
const bar b;
};
foo f;
::new (&f) foo{{3}};
这里我有一个常量intf.b.x
,我销毁它并构造一个值为3的新常量。
char buff[sizeof(foo)];
foo const* pf=::new(buff)foo;
foo const* pf2=::new(buff)foo{{3}};
也许也可以与工会合作。