¿Es seguro llamar a la ubicación nueva en 'this' para un objeto trivial?

20

Sé que esta pregunta ya se hizo varias veces, pero no pude encontrar una respuesta para este caso en particular.

Digamos que tengo una clase trivial que no posee ningún recurso y tiene un destructor vacío y un constructor predeterminado. Tiene un puñado de variables miembro con inicialización en clase; ninguno de ellos lo es const.

Quiero reinicializar y objetar tal clase sin escribir el deInitmétodo a mano. ¿Es seguro hacerlo así?

void A::deInit()
{
  new (this)A{};
}

No puedo ver ningún problema con él: el objeto siempre está en estado válido, thistodavía apunta a la misma dirección; pero es C ++, así que quiero estar seguro.

Amomum
fuente
2
¿Hay algún miembro del objeto constante?
NathanOliver
2
Si esto es válido, ¿sería equivalente a *this = A{};?
Kevin
2
@Amomum *this = A{};significa, this->operator=(A{});es decir, crear un objeto temporal y asignarlo *this, reemplazando los valores de todos los miembros de datos con los valores temporales. Como eso es lo que quieres y es (en mi opinión) más legible que una ubicación nueva, preferiría eso.
Kevin
1
@ Kevin oh, mi mal, tienes razón. Que - creo que debería ser igual si se copia la copia?
Amomum
1
en lugar de explicar la clase en palabras, es mucho mejor escribir la clase completa, y no solo un método. ver sscce.org
BЈовић

Respuestas:

17

De manera similar a la legalidad de delete this, la colocación de nuevos thistambién está permitida hasta donde yo sé. Además, con respecto a si this, u otros punteros / referencias preexistentes pueden usarse después, hay algunas restricciones:

[basic.life]

Si, después de que la vida útil de un objeto ha finalizado y antes del almacenamiento que el objeto ocupado se reutiliza o libera, se crea un nuevo objeto en la ubicación de almacenamiento que ocupó el objeto original, un puntero que apuntaba al objeto original, una referencia que referido al objeto original, o el nombre del objeto original se referirá automáticamente al nuevo objeto y, una vez que ha comenzado la vida útil del nuevo objeto, puede usarse para manipular el nuevo objeto, si:

  • el almacenamiento para el nuevo objeto se superpone exactamente a la ubicación de almacenamiento que ocupaba el objeto original, y
  • el nuevo objeto es del mismo tipo que el objeto original (ignorando los calificadores cv de nivel superior), y
  • el tipo del objeto original no está calificado const y, si es un tipo de clase, no contiene ningún miembro de datos no estático cuyo tipo esté calificado const o un tipo de referencia, y
  • ni el objeto original ni el nuevo objeto es un subobjeto potencialmente superpuesto ([intro.object]).

Los dos primeros están satisfechos en este ejemplo, pero los dos últimos deberán tenerse en cuenta.

Con respecto al tercer punto, dado que la función no está calificada para const, debería ser bastante seguro asumir que el objeto original no es const. La falla está en el lado de la persona que llama si la constidad ha sido desechada. Con respecto al miembro const / reference, creo que puede verificarse afirmando que esto es asignable:

static_assert(std::is_trivial_v<A> && std::is_copy_assignable_v<A>);

Por supuesto, dado que la asignabilidad es un requisito, en su lugar, simplemente puede usar *this = {};lo que esperaría para producir el mismo programa. Un caso de uso quizás más interesante podría ser reutilizar la memoria de *thisun objeto de otro tipo (lo que no cumpliría los requisitos para su uso this, al menos sin reinterpretar + lavar).

Similar a delete this, la ubicación nueva a thisdifícilmente podría describirse como "segura".

eerorika
fuente
Interesante. ¿Es posible asegurarse de que todas estas condiciones se cumplan con static_assert en algunos rasgos de tipo? No estoy seguro si hay uno sobre los miembros de const ...
Amomum
1
@Amomum No creo que ser subobjeto sea algo que se pueda probar. Los miembros constantes o de referencia harían que la clase no sea asignable, lo que no creo que pueda suceder de otra manera para una clase trivial.
eerorika
¿Esto significa que el alias estricto entra en juego con respecto a la constidad? ¿Puedes dar un ejemplo de dónde esto podría entrar en juego?
Darune
1
@darune Cree un objeto constante, colocación nueva en él, use el nombre original del objeto (o un puntero o referencia preexistente) y el comportamiento será indefinido. Más o menos lo mismo que la optimización estricta de alias, si no la misma.
eerorika
1
El inverso de delete ptres new T(). El inverso de new(ptr)T{}es ptr->~T();. stackoverflow.com/a/8918942/845092
Mooing Duck
7

Las reglas que cubren esto están en [basic.life] / 5

Un programa puede finalizar la vida útil de cualquier objeto reutilizando el almacenamiento que ocupa el objeto o llamando explícitamente al destructor para un objeto de un tipo de clase. Para un objeto de un tipo de clase, no se requiere que el programa llame al destructor explícitamente antes de que el almacenamiento que ocupa el objeto sea reutilizado o liberado; sin embargo, si no hay una llamada explícita al destructor o si no se usa una expresión de eliminación para liberar el almacenamiento, el destructor no se llama implícitamente y cualquier programa que dependa de los efectos secundarios producidos por el destructor tiene un comportamiento indefinido.

y [basic.life] / 8

Si, después de que la vida útil de un objeto ha finalizado y antes del almacenamiento que el objeto ocupado se reutiliza o libera, se crea un nuevo objeto en la ubicación de almacenamiento que ocupó el objeto original, un puntero que apuntaba al objeto original, una referencia que referido al objeto original, o el nombre del objeto original se referirá automáticamente al nuevo objeto y, una vez que ha comenzado la vida útil del nuevo objeto, puede usarse para manipular el nuevo objeto, si:

  • el almacenamiento para el nuevo objeto se superpone exactamente a la ubicación de almacenamiento que ocupaba el objeto original, y

  • el nuevo objeto es del mismo tipo que el objeto original (ignorando los calificadores cv de nivel superior), y

  • el tipo del objeto original no está calificado const y, si es un tipo de clase, no contiene ningún miembro de datos no estático cuyo tipo esté calificado const o un tipo de referencia, y

  • ni el objeto original ni el nuevo objeto es un subobjeto potencialmente superpuesto ([intro.object]).

Como su objeto es trivial, no tiene que preocuparse por [basic.life] / 5 y siempre y cuando satisfaga los puntos de bala de [basic.life] / 8, entonces es seguro.

NathanOliver
fuente