std::launder
| Definido en el archivo de encabezado <new>
|
||
template <typename T> constexpr T* launder(T* p) noexcept; |
(desde C++17) (hasta C++20) |
|
template <typename T> [[nodiscard]] constexpr T* launder(T* p) noexcept; |
(desde C++20) | |
Obtiene un puntero al objeto ubicado en la dirección representada por p.
Formalmente, dado
- el puntero
prepresenta a la direcciónAde un byte en memoria - un objeto
Xestá ubicado en la direcciónA Xestá dentro de su duración- el tipo de
Xes el mismo que el deT, ignorando calificadores-cv en todos los niveles - cada byte que sería accesible mediante el resultado es accesible mediante p (los bytes son accesibles mediante un puntero que apunta a un objeto
Ysi esos bytes están dentro del almacenamiento de un objetoZque es de puntero interconvertible conY, de dentro del array inmediatamente circundante del queZes un elemento)
Entonces std::launder(p) devuelve un valor de tipo T* que apunta al objeto X. De otra forma, el comportamiento es indefinido.
El programa está mal formado si T es un tipo de función o void (posiblemente calificado-cv).
std::launder puede utilizarse en una expresión constante central si el valor de su argumento puede utilizarse en una expresión constante central.
Notas
std::launder no tiene efecto en su argumento. Su valor de retorno debe de usarse para acceder al objeto. Por lo tanto, siempre es un error descartar el valor de retorno.
Usos típicos de std::launder incluyen:
- Obtención de 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);
- Obtención de un puntero a un objeto creado mediante
newde ubicación de un puntero a un objeto que proporciona almacenamiento para ese objeto.
La restricción de accesibilidad asegura que std::launder no pueda usarse para acceder a bytes no accesibles a través del puntero original, lo que interfiere con el análisis de escape del compilador.
int x[10];
auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0])); // de acuerdo
int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0]));
// Comportamiento indefinido: x2[1] sería accesible mediante el puntero resultante a x2[0]
// pero no es accesible desde la fuente
struct X { int a[10]; } x3, x4[2]; // diseño estándar; suponer que no hay relleno
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // de acuerdo
auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0]));
// Comportamiento indefinido: x4[1] sería accesible mediante el puntero resultante a x4[0].a
// (que es de puntero interconvertible 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 mediante el puntero resultante a x5.a
// pero no es accesible desde la fuente
Ejemplo
#include <new>
#include <cstddef>
#include <cassert>
struct X {
const int n; // nota: X tiene un miembro const
int m;
};
struct Y {
int z;
};
struct A {
virtual int transfigurar();
};
struct B : A {
int transfigurar() override { new(this) A; return 2; }
};
int A::transfigurar() { new(this) B; return 1; }
static_assert(sizeof(B) == sizeof(A));
int main()
{
X *p = new X{3, 4};
const int a = p->n;
X* np = new (p) X{5, 6}; // p no apunta al nuevo objeto porque X::n es const; np lo hace
const int b = p->n; // comportamiento indefinido
const int c = p->m; // comportamiento indefinido (aun cuando m es no-const, p no puede usarse)
const int d = std::launder(p)->n; // de acuerdo, std::launder(p) apunta al nuevo objeto
const int e = np->n; // de acuerdo
alignas(Y) std::byte s[sizeof(Y)];
Y* q = new(&s) Y{2};
const int f = reinterpret_cast<Y*>(&s)->z; // Acceso al miembro de clase es comportamiento indefinido:
// reinterpret_cast<Y*>(&s) tiene valor "puntero a s"
// y no apunta al objeto Y
const int g = q->z; // de acuerdo
const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // de acuerdo
A i;
int n = i.transfigurar();
// int m = i.transfigurar(); // comportamiento indefinido
int m = std::launder(&i)->transfigurar(); // de acuerdo
assert(m + n == 3);
}
Informes de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
| ID | Aplicado a | Comportamiento según lo publicado | Comportamiento correcto |
|---|---|---|---|
| LWG 2859 | C++17 | La definición de accesible no consideró aritmética de puntero desde el objeto de puntero interconvertible. |
Incluido. |