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)
Definido en el encabezado <new>
template < class T >
constexpr T * launder ( T * p ) noexcept ;
(desde C++17)

Barrera de desvirtualización con respecto a p . Retorna un puntero a un objeto en la misma dirección que p representa, mientras que el objeto puede ser un nuevo subobjeto de clase base cuya clase más derivada es diferente de la del objeto original * p .

Formalmente, dado

  • el puntero p representa la dirección A de un byte en memoria
  • un objeto x está ubicado en la dirección A
  • x está dentro de su tiempo de vida
  • el tipo de x es el mismo que T , ignorando calificadores cv en cada nivel
  • cada byte que sería accesible a través del resultado es accesible a través de p (los bytes son accesibles a través de un puntero que apunta a un objeto y si esos bytes están dentro del almacenamiento de un objeto z que es intercambiable por puntero con y , o dentro del array inmediatamente contenedor del cual z es un elemento).

Entonces std :: launder ( p ) devuelve un valor de tipo T* que apunta al objeto x . De lo contrario, el comportamiento es indefinido.

El programa está mal formado si T es un tipo de función o (posiblemente calificado con cv) void .

std::launder puede utilizarse en una expresión constante principal si y solo si el valor (convertido) de su argumento puede utilizarse en lugar de la invocación de la función. En otras palabras, std::launder no relaja las restricciones en la evaluación constante.

Notas

std::launder no tiene efecto sobre su argumento. Su valor de retorno debe utilizarse para acceder al objeto. Por lo tanto, siempre es un error descartar el valor de retorno.

Usos típicos de std::launder incluyen:

  • Obtener un puntero a un objeto creado en el almacenamiento de un objeto existente del mismo tipo, donde los punteros al objeto antiguo no pueden ser reutilizados (por ejemplo, porque cualquiera de los objetos es un subobjeto de clase base);
  • Obtener un puntero a un objeto creado mediante placement new desde un puntero a un objeto que proporciona almacenamiento para ese objeto.

La restricción de alcanzabilidad garantiza que std::launder no pueda utilizarse para acceder a bytes no accesibles a través del puntero original, interfiriendo así con el análisis de escape del compilador.

int x[10];
auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0])); // CORRECTO
int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0]));
// Comportamiento indefinido: x2[1] sería accesible a través del puntero resultante a x2[0]
// pero no es accesible desde la fuente
struct X { int a[10]; } x3, x4[2]; // diseño estándar; asumir sin relleno
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // CORRECTO
auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0]));
// Comportamiento indefinido: x4[1] sería accesible a través del puntero resultante a x4[0].a
// (que es intercambiable por puntero con x4[0]) pero no es accesible desde la fuente
struct Y { int a[10]; double y; } x5;
auto p5 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0]));
// Comportamiento indefinido: x5.y sería accesible a través del puntero resultante a x5.a
// pero no es accesible desde la fuente

Ejemplo

#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()
{
    // Caso 1: el nuevo objeto no pudo ser reemplazado transparentemente porque
    // es un subobjeto base pero el objeto antiguo es un objeto completo.
    Base base;
    int n = base.transmogrify();
    // int m = base.transmogrify(); // comportamiento indefinido
    int m = std::launder(&base)->transmogrify(); // OK
    assert(m + n == 3);
    // Caso 2: acceso a un nuevo objeto cuyo almacenamiento es proporcionado
    // por un array de bytes a través de un puntero al 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; // El acceso a miembro de clase es comportamiento
                                               // indefinido: reinterpret_cast<Y*>(&s)
                                               // tiene valor "puntero a s" y no
                                               // apunta a un objeto Y
    const int g = q->z; // OK
    const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // OK
    [](...){}(f, g, h); // provoca efecto [[maybe_unused]]
}

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.

DR Aplicado a Comportamiento publicado Comportamiento correcto
LWG 2859 C++17 la definición de reachable no consideraba la aritmética
de punteros desde objeto interconvertible por puntero
incluido
LWG 3495 C++17 std::launder podría hacer que un puntero a un miembro
inactivo fuera dereferenciable en expresión constante
prohibido