Namespaces
Variants

std:: launder

From cppreference.net
Utilities library
Memory management library
( exposition only* )
Allocators
Uninitialized memory algorithms
Constrained uninitialized memory algorithms
Memory resources
Uninitialized storage (until C++20)
( until C++20* )
( until C++20* )
( until C++20* )

Garbage collector support (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
定义于头文件 <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 指针可互转换 的对象 z 的存储空间内,或位于直接包含 z 作为元素的数组中,则这些字节可通过指向对象 y 的指针访问)。

那么 std :: launder ( p ) 将返回一个指向对象 x T* 类型值。否则,其行为是未定义的。

如果 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])); // 正确
int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0]));
// 未定义行为:x2[1] 可以通过结果指针访问到 x2[0],但无法从源指针访问
struct X { int a[10]; } x3, x4[2]; // 标准布局;假设无填充
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // 正确
auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0]));
// 未定义行为:x4[1] 可以通过结果指针访问到 x4[0].a
//(与 x4[0] 指针可互转换)但无法从源指针访问
struct Y { int a[10]; double y; } x5;
auto p5 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0]));
// 未定义行为:x5.y 可以通过结果指针访问到 x5.a
// 但无法从源指针访问

示例

#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()
{
    // 情况1:新对象无法透明替换,因为它是基类子对象而旧对象是完整对象
    Base base;
    int n = base.transmogrify();
    // int m = base.transmogrify(); // 未定义行为
    int m = std::launder(&base)->transmogrify(); // 正确
    assert(m + n == 3);
    // 情况2:通过指向数组的指针访问由字节数组提供存储的新对象
    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; // 类成员访问是未定义行为:reinterpret_cast<Y*>(&s)
                                               // 的值为"指向s的指针",并不指向Y对象
    const int g = q->z; // 正确
    const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // 正确
    [](...){}(f, g, h); // 产生[[maybe_unused]]效果
}

缺陷报告

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

缺陷报告 适用范围 发布时行为 正确行为
LWG 2859 C++17 可达 的定义未考虑来自指针可互转换对象的
指针算术运算
已包含
LWG 3495 C++17 std::launder 可能使指向非活跃成员的指针
在常量表达式中可解引用
已禁止