c++ - alineado_storage y aliasing estricto
undefined-behavior language-lawyer (1)
ABICT su uso es seguro.
- La ubicación nueva de un objeto de tipo T creará un objeto a partir de la dirección que se pasa.
§5.3.4 / 10 dice:
Una nueva expresión pasa la cantidad de espacio solicitado a la función de asignación como el primer argumento de tipo std :: size_t. Ese argumento no será menor que el tamaño del objeto que se está creando; puede ser mayor que el tamaño del objeto que se está creando solo si el objeto es una matriz.
Para un objeto que no es de matriz, el tamaño asignado no puede ser mayor que el tamaño del objeto, por lo que la representación del objeto debe comenzar al principio de la memoria asignada para poder ajustarse.
Colocación nueva devuelve el puntero pasado (ver § 18.6.1.3/2) como resultado de la "asignación", por lo que la representación del objeto del objeto construido comenzará en esa dirección.
-
static_cast<>
y las conversiones implícitas entreT*
type yvoid*
convierten entre un puntero al objeto y un puntero a su almacenamiento, si el objeto es un objeto completo.
§4.10 / 2 dice:
Un prvalor de tipo "puntero a cv T", donde T es un tipo de objeto, se puede convertir a un prvalor de tipo "puntero a cv void". El resultado de convertir un "puntero a cv T" en un "puntero a cv void" apunta al inicio de la ubicación de almacenamiento donde reside el objeto de tipo T, como si el objeto fuera el objeto más derivado (1.8) de tipo T [...]
Esto define la conversión implícita para convertir como se indica. Además §5.2.9 [expr.static.cast] / 4 define static_cast<>
para conversiones explícitas, donde existe una conversión implícita para tener el mismo efecto que la conversión implícita:
De lo contrario, una expresión
e
se puede convertir explícitamente a un tipoT
utilizando unstatic_cast
de la formastatic_cast<T>(e)
si la declaraciónT t(e);
está bien formado, para alguna variable temporal inventadat
(8.5). El efecto de tal conversión explícita es el mismo que realizar la declaración y la inicialización y luego usar la variable temporal como resultado de la conversión. [...]
Para el static_cast<>
inverso static_cast<>
(de void*
a T*
), §5.2.9 / 13 estados:
Un prvalor de tipo "puntero a cv1 void" se puede convertir a un prvalor de tipo "puntero a cv2 T", donde T es un tipo de objeto y cv2 tiene la misma calificación cv que la cv1 o una calificación mayor. [...] Un valor del tipo puntero a objeto convertido a "puntero a cv void" y viceversa, posiblemente con una calificación cv diferente, tendrá su valor original.
Por lo tanto, si tiene un void*
apunta al almacenamiento del objeto T
(que es el valor del puntero que resultaría de la conversión implícita de un T*
al objeto, entonces un static_cast
de eso a un T*
producirá un puntero válido) al objeto.
Volviendo a su pregunta, los puntos anteriores implican que si tiene
typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type t_;
void * pvt_ = &t_;
T* pT = new (&t_) T(args...);
void * pvT = pT;
entonces
- el almacenamiento de
*pT
superpone exactamente los bytes del primer tamaño (T) del almacenamiento det_
, de modo quepvT == pvt_
-
pvt_ == static_cast<void*>(&t_)
-
static_cast<T*>(pvT) == pT
- Tomados juntos que produce
static_cast<T*>(static_cast<void*>(&t_)) == pT
Actualmente estoy utilizando align_storage para implementar un tipo ''Opcional'' similar al de boost :: opcional. Para lograr esto tengo un miembro de la clase así:
typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type t_;
Utilizo la ubicación nueva para crear el objeto, sin embargo, no almaceno el puntero devuelto en ningún lugar. En su lugar, accedo al tipo subyacente del objeto en todas mis funciones miembro de esta manera (obviamente con verificaciones para garantizar que el objeto sea válido a través de una bandera booleana también almacenada en mi tipo Opcional):
T const* operator->() const {
return static_cast<T const*>(static_cast<void const*>(&t_));
}
Mi pregunta es si esto es seguro. Mi entendimiento es que mi uso de la ubicación nueva cambia el "tipo dinámico" del objeto, y mientras continúe accediendo a la memoria usando ese tipo, estaré bien. Sin embargo, no tengo claro si tengo que mantener el puntero devuelto desde la ubicación nueva o si puedo emitir el tipo subyacente cada vez que necesito acceder a él. He leído la sección 3.10 de la norma C ++ 11, sin embargo, no estoy lo suficientemente fluido como para estar seguro.
Si es posible, me sentiría mejor si pudiera dar referencia al estándar en su respuesta (me ayuda a dormir por la noche: P).