reinterpret_cast creando un objeto trivially predeterminado-construible

cppreference indica que:

Los objetos con constructores triviales por defecto se pueden crear utilizando reinterpret_cast en cualquier almacenamiento alineado adecuadamente, por ejemplo, en la memoria asignada con std::malloc .

Esto implica que el siguiente es un código bien definido:

 struct X { int x; }; alignas(X) char buffer[sizeof(X)]; // (A) reinterpret_cast(buffer)->x = 42; // (B) 

Tres preguntas siguen:

  1. Es esa cita correcta?
  2. En caso afirmativo, ¿en qué punto comienza la vida de la X ? Si está en la línea (B) , ¿es el reparto lo que se considera adquirir almacenamiento? Si está en la línea (A) , ¿qué pasaría si hubiera una twig entre (A) y (B) que construiría condicionalmente una X o alguna otra vaina, Y ?
  3. ¿Hay algo que cambie entre C ++ 11 y C ++ 1z a este respecto?

Tenga en cuenta que este es un enlace antiguo. La redacción fue modificada en respuesta a esta pregunta. Ahora dice:

Sin embargo, a diferencia de C, los objetos con constructores triviales predeterminados no pueden crearse simplemente reinterpretando el almacenamiento adecuadamente alineado, como la memoria asignada con std::malloc : placement-new es necesario para presentar formalmente un nuevo objeto y evitar el posible comportamiento indefinido.

No hay ningún objeto X , vivo o no, por lo que pretender que hay uno da como resultado un comportamiento indefinido.

[intro.object] / 1 explica de forma exhaustiva cuándo se crean los objetos:

Un objeto es creado por una definición ([basic.def]), por una nueva-expresión ([expr.new]), al cambiar implícitamente el miembro activo de una unión ([class.union]), o cuando un objeto temporal se crea ([conv.rval], [class.temporary]).

Con la adopción de P0137R1 , este párrafo es la definición del término “objeto”.

¿Hay una definición de un objeto X ? No. ¿Hay una nueva expresión ? No. ¿Hay una unión? No. ¿Hay un constructo de lenguaje en su código que crea un objeto X temporal? No.

Cualquier cosa que [basic.life] diga sobre la vida útil de un objeto con una inicialización vacía es irrelevante. Para que se aplique, debes tener un objeto en primer lugar. Tu no

C ++ 11 tiene aproximadamente el mismo párrafo, pero no lo usa como la definición de “objeto”. No obstante, la interpretación es la misma. La interpretación alternativa, tratar a [basic.life] como crear un objeto tan pronto como se obtenga un almacenamiento adecuado, significa que está creando objetos de Schrödinger * , lo que contradice N3337 [intro.object] / 6 :

Dos objetos que no son campos de bits pueden tener la misma dirección si uno es un subobjeto de la otra, o si al menos uno es un subobjeto de clase base de tamaño cero y son de tipos diferentes; de lo contrario, deberán tener direcciones distintas.


* El almacenamiento con la alineación y el tamaño adecuados para un tipo T es, por definición, el almacenamiento con la alineación y el tamaño adecuados para cualquier otro tipo cuyos requisitos de tamaño y alineación sean iguales o menores que los de T Por lo tanto, esa interpretación significa que la obtención simultánea del almacenamiento crea un conjunto infinito de objetos con diferentes tipos en dicho almacenamiento, todos con la misma dirección.

Este análisis se basa en n4567 y utiliza números de sección a partir de él.

§5.2.10 / 7: cuando un prvalue v del tipo de puntero de objeto se convierte en el puntero de objeto tipo “puntero a cv T”, el resultado es static_cast(static_cast(v)) .

Entonces, en este caso, reinterpret_cast(buffer) es lo mismo que static_cast(static_cast(buffer)) . Eso nos lleva a mirar las partes relevantes sobre static_cast :

§5.2.9 / 13: Un prvalue de tipo “puntero a cv1 void” se puede convertir a un prvalue de tipo “puntero a cv2 T”, donde T es un tipo de objeto y cv2 es la misma calificación cv, o mayor cv-qualification than, cv1 . El valor del puntero nulo se convierte en el valor del puntero nulo del tipo de destino. Si el valor del puntero original representa la dirección A de un byte en la memoria y A satisface el requisito de alineación de T , entonces el valor del puntero resultante representa la misma dirección que el valor del puntero original, es decir, A

Creo que eso es suficiente para decir que la cita original es más o menos correcta, esta conversión da resultados definidos.

En cuanto a la vida, depende de qué vida estás hablando. El elenco crea un nuevo objeto de tipo puntero, un temporal, que tiene una duración desde la línea donde se ubica el elenco, y termina cuando sale del scope. Si tiene dos conversiones diferentes que suceden condicionalmente, cada puntero tiene un tiempo de vida que comienza desde la ubicación del elenco que lo creó.

Ninguno de estos factores afecta la duración del objeto que proporciona el almacenamiento subyacente, que sigue siendo el buffer , y tiene exactamente la misma duración, independientemente de si se crea un puntero (del mismo tipo o convertido) en ese almacenamiento o no.