基本生活/8告诉我们,我们可以在一个对象的生命周期结束后,使用它所占用的存储空间来创建一个新的对象,并使用它原来的名称来引用它,除非:
\n\n\n\n
\n- 原始对象的类型不是 const 限定的,并且,如果是类类型,则不包含任何类型为 const 限定的非静态数据成员或引用类型,并且 [...]
\n
强调我的
\n但是,就在其下方,我们可以看到一条注释:
\n\n\n\n
\n- 如果不满足这些条件,则可以通过调用从表示其存储地址的指针获得指向新对象的指针
\nstd\xe2\x80\x8b::\xe2\x80\x8blaunder
这解释了的目的std::launder
。我们可以有一个类类型const
,并使用placement-new 来创建一个具有不同内部值的新对象。
让我惊讶的是第一句话的第一部分。它清楚地表明,如果存储是const
(不一定包含const
成员,但整个对象被声明为const
),我们不能用它来引用一个新对象,但这可能意味着std::launder
可以修复它。
但我们如何创建这样的对象呢?最简单的例子失败了:
\nconst auto x = 5;\nnew (&x) auto(10);\n
Run Code Online (Sandbox Code Playgroud)\n这是因为我们不能用作const void*
新放置的缓冲区。我们可以const_cast
做到,但抛弃“真实”const
性就是未定义的行为。我相信这里也是如此。
如果只是禁止使用const
对象作为放置新缓冲区,我会理解,但如果是这样,那么第一个引用中强调的部分的目的是什么?我们能否真正利用重用const
对象的存储重用于不同的对象吗?
std::launder
有一个先决条件:从将要返回的指针可到达的所有字节都可以通过传递的指针到达。
我的理解是,这是为了允许编译器优化,以便例如
struct A {
int a[2];
int b;
};
void f(int&);
int g() {
A a{{0,0},2};
f(a.a[0]);
return a.b;
}
Run Code Online (Sandbox Code Playgroud)
可以优化为始终返回2
。(请参阅指针可互换性与具有相同地址和能否使用 std::launder 将对象指针转换为其封闭数组指针?)
这是否意味着可达性先决条件也应该适用于placement-new?否则有什么阻止我写f
如下吗?:
void f(int& x) {
new(&x) A{{0,0},0};
}
Run Code Online (Sandbox Code Playgroud)
的地址与 的地址a.a[0]
相同,a
并且新A
对象可以透明地替换为旧A
对象,因此a.b
ing
现在应该是0
。
我正在阅读 cppreference ,在std::aligned_storage的示例中有一个向量/数组类的示例:
template<class T, std::size_t N>
class static_vector
{
// properly aligned uninitialized storage for N T's
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
// IF you want to see possible implementation of aligned storage see link.
// It's very simple and small, it's just a buffer
std::size_t m_size = 0;
public:
// Create an object in aligned storage
template<typename ...Args> void emplace_back(Args&&... args)
{
if( m_size >= N ) // possible error handling
throw std::bad_alloc{};
// construct value in memory …
Run Code Online (Sandbox Code Playgroud) 考虑以下示例代码:
struct X { const int n; };
union U { X x; float f; };
void fun() {
U u = {{ 1 }};
u.f = 5.f; // OK, creates new subobject of 'u'
X *p = new (&u.x) X {2}; // OK, creates new subobject of 'u'
if(*std::launder(&u.x.n) == 2){// condition is true because of std::launder
std::cout << u.x.n << std::endl; //UB here?
}
}
Run Code Online (Sandbox Code Playgroud)
函数会fun
根据语言标准打印什么?换句话说,std::launder
last 的效果是否超出了它被调用的表达式?std::launder
或者,我们每次需要访问更新后的值时都必须使用u.x.n
?
struct X { int n; };
const X *p = new const X{3}; // #1
new (const_cast<X*>(p)) const X{5}; // #2
const int c = std::launder(p)->n;
Run Code Online (Sandbox Code Playgroud)
假设在 处创建的对象#1
名为 ,obj1
而在 处创建的对象#2
名为obj2
。的前提std::launder
是
[ptr.launder] p2链接
p代表内存中一个字节的地址A。处于其生命周期内且类型与 T 类似的对象 X 位于地址 A 处。通过结果可访问的所有存储字节都可通过 p (见下文)访问。
如果存在对象 Z,则可以通过指向对象 Y 的指针值访问存储 b 的字节,并且该对象可与 Y 进行指针互换,这样 b 位于 Z 占用的存储空间内,或者如果 Z 则位于直接封闭的数组对象内是一个数组元素。
这个规则有点晦涩难懂。以下解释正确吗?
obj2
sizeof(X)
将占用以 开头的字节数A
。将Y
(指向的对象std::launder(p)
) 和 …
假设您有一个struct
with 元素,您不想(不能)在初始化阶段初始化它,但它不是默认可初始化的。我想出了一种使用方法std::launder
:为结构分配一个空间,然后使用洗衣槽在正确的位置分配一个值。例如,
#include <cassert>
#include <cstddef>
#include <iostream>
#include <new>
struct T {
int t;
};
struct S {
int x;
T t;
S() = delete;
S(int x) : x(x) {}
};
int main() {
alignas(S) std::byte storage[sizeof(S)];
S s0(42);
// 0. I believe that the commented line is dangerous, although it compiles well.
// *std::launder(reinterpret_cast<S*>(storage)) = s0;
S *ps = std::launder(reinterpret_cast<S*>(storage));
ps->x = 42;
ps->t = T{};
{ // 1. Is this safe?
S …
Run Code Online (Sandbox Code Playgroud) 我认为我的代码片段不应产生断言失败。但是使用带有 O3 选项的 gcc 13.2 会失败。
所以我认为这里是未定义的行为。clang 和 gcc 与 O0 工作得很好。如果我将析构函数更改为其他方法 - 断言失败就会消失。
#include <cstddef>
#include <new>
#include <cassert>
struct Foo
{
~Foo()
{
destructed = true;
}
bool destructed = false;
};
int main()
{
alignas(alignof(Foo)) std::byte buffer[sizeof(Foo)];
Foo* p1 = std::launder(reinterpret_cast<Foo*>(buffer));
new (p1) Foo();
assert(p1->destructed == false);
Foo* p3 = std::launder(reinterpret_cast<Foo*>(buffer));
p3->~Foo();
//////////// Assertion failure ?????????????????????????
assert(p3->destructed == true);
return 0;
}
Run Code Online (Sandbox Code Playgroud)