std::launder
| Definido en la cabecera <new>
|
||
template< class T >
constexpr T* launder( T* p ) noexcept;
|
(desde C++17) | |
Barrera de desvirtualización con respecto a p. Devuelve 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 *p original.
Formalmente, dado
- el puntero
prepresenta la direcciónAde un byte en memoria - un objeto
xestá ubicado en la direcciónA xestá dentro de su tiempo de vida- el tipo de
xes el mismo queT, 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
ysi esos bytes están dentro del almacenamiento de un objetozque es interconvertible por puntero cony, o dentro del array contenedor inmediato del cualzes un elemento).
Entonces std::launder(p) devuelve un valor de tipo T* que apunta al objeto x. En caso contrario, el comportamiento no está definido.
El programa está mal formado si T es un tipo de función o (posiblemente cv-calificado) void.
std::launder puede ser usado en una expresión constante central si y solo si el valor (convertido) de su argumento puede ser usado 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
newdesde 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 |