命名空间
变体
操作

std::launder

来自 cppreference.cn
< cpp‎ | utility
 
 
 
内存管理库
(仅用于说明*)
未初始化内存算法
(C++17)
(C++17)
(C++17)
约束的未初始化
内存算法
C 库

分配器
内存资源
垃圾回收支持
(C++11)(直到 C++23)
(C++11)(直到 C++23)
(C++11)(直到 C++23)
(C++11)(直到 C++23)
(C++11)(直到 C++23)
(C++11)(直到 C++23)
未初始化存储
(直到 C++20*)
(直到 C++20*)
显式生命周期管理
 
 
定义于头文件 <new>
template< class T >
constexpr T* launder( T* p ) noexcept;
(自 C++17 起)

关于 p 的去虚化栅栏。返回一个指向与 p 所代表的地址相同的对象的指针,而该对象可以是一个新的基类子对象,其最派生类与原始 *p 对象的类不同。

形式上,给定

  • 指针 p 代表内存中字节的地址 A
  • 对象 x 位于地址 A
  • x 在其生命周期
  • x 的类型与 T 相同,忽略每个级别的 cv 限定符
  • 每个可以通过结果访问的字节都可以通过 p 访问(字节可以通过指向对象 y 的指针访问,如果这些字节在与 y 指针可互转换 的对象 z 的存储空间内,或者在 z 是元素的直接外围数组内)。

然后 std::launder(p) 返回类型为 T* 的值,该值指向对象 x。否则,行为未定义。

如果 T 是函数类型或(可能带有 cv 限定的)void,则程序是非良构的。

如果且仅当其参数的(转换后的)值可以代替函数调用使用时,std::launder 才可以用于核心常量表达式中。换句话说,std::launder 不会放宽常量求值中的限制。

[编辑] 注解

std::launder 对其参数没有影响。必须使用其返回值来访问对象。因此,丢弃返回值始终是错误的。

std::launder 的典型用法包括

  • 获取指向在相同类型的现有对象的存储空间中创建的对象的指针,其中旧对象的指针不能重用(例如,因为两个对象都是基类子对象);
  • 从提供该对象存储空间的对象的指针,获取指向通过放置 new 创建的对象的指针。

可达性 限制确保 std::launder 不能用于访问通过原始指针无法访问的字节,从而干扰编译器的逃逸分析。

int x[10];
auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0])); // OK
 
int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0]));
// Undefined behavior: x2[1] would be reachable through the resulting pointer to x2[0]
// but is not reachable from the source
 
struct X { int a[10]; } x3, x4[2]; // standard layout; assume no padding
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // OK
auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0]));
// Undefined behavior: x4[1] would be reachable through the resulting pointer to x4[0].a
// (which is pointer-interconvertible with x4[0]) but is not reachable from the source
 
struct Y { int a[10]; double y; } x5;
auto p5 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0]));
// Undefined behavior: x5.y would be reachable through the resulting pointer to x5.a
// but is not reachable from the source

[编辑] 示例

#include <cassert>
#include <cstddef>
#include <new>
 
struct Base
{
    virtual int transmogrify();
};
 
struct Derived : Base
{
    int transmogrify() override
    {
        new(this) Base;
        return 2;
    }
};
 
int Base::transmogrify()
{
    new(this) Derived;
    return 1;
}
 
static_assert(sizeof(Derived) == sizeof(Base));
 
int main()
{
    // Case 1: the new object failed to be transparently replaceable because
    // it is a base subobject but the old object is a complete object.
    Base base;
    int n = base.transmogrify();
    // int m = base.transmogrify(); // undefined behavior
    int m = std::launder(&base)->transmogrify(); // OK
    assert(m + n == 3);
 
    // Case 2: access to a new object whose storage is provided
    // by a byte array through a pointer to the array.
    struct Y { int z; };
    alignas(Y) std::byte s[sizeof(Y)];
    Y* q = new(&s) Y{2};
    const int f = reinterpret_cast<Y*>(&s)->z; // Class member access is undefined
                                               // behavior: reinterpret_cast<Y*>(&s)
                                               // has value "pointer to s" and does
                                               // not point to a Y object
    const int g = q->z; // OK
    const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // OK
 
    [](...){}(f, g, h); // evokes [[maybe_unused]] effect
}

[编辑] 缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。

DR 应用于 已发布行为 正确行为
LWG 2859 C++17 可达性 的定义未考虑来自指针可互转换对象的指针算术
算术
已包括
LWG 3495 C++17 std::launder 可能会使指向非活动成员的指针在常量表达式中可解引用
成员可解引用
已禁止