2012-01-12 15 views
28

A veces es bueno empezar de nuevo. En C++ puedo emplear esta sencilla maniobra siguiente:Destruye y luego construye un objeto nuevo usando la misma variable

{ 

    T x(31, Blue, false); 

    x.~T();      // enough with the old x 

    ::new (&x) T(22, Brown, true); // in with the new! 

    // ... 
} 

Al final del alcance, el destructor se ejecutará una vez más y todo se vea bien. (Digamos también T es un poco especial y no le gusta ser asignado, ni mucho menos intercambiado.) Pero algo me dice que no siempre es sin riesgo destruir todo e intentar de nuevo. ¿Hay una posible captura con este enfoque?

+7

¿Por qué diablos quieres hacer esto? –

+0

Este es un enfoque caprichoso de la pedagogía. – Crashworks

+7

¿Qué pasa con solo 'x = T (22, Marrón, verdadero);'? –

Respuesta

28

creo que la única manera de hacer esto realmente es seguro de usar para exigir al constructor llamado a ser noexcept, por ejemplo mediante la adición de un static_assert:

static_assert(noexcept(T(22, Brown, true)), "The constructor must be noexcept for inplace reconstruction"); 
T x(31, Blue, false); 
x.~T(); 
::new (&x) T(22, Brown, true); 

Por supuesto, esto sólo funcionará para C++ 11.

+7

Muy buen uso de 'noexcept' - ¡Nunca me di cuenta de que podría usarse interrogativamente! –

+1

Puede empaquetar la llamada 'static_assert', pseudo-destructor y ubicar todo nuevo en una función' reconstruir', utilizando el reenvío perfecto para el último paso. Pero todavía creo que es una forma horrible. – Potatoswatter

17

Si el constructor de T lanza en la segunda construcción, tiene un problema. Si te gustan los enfoques de fuerza bruta, verifica esto:

T x(31, Blue, false); 
x.~T(); 
const volatile bool _ = true; 
for(;_;){ 
    try{ 
    ::new (&x) T(22, Brown, true); 
    break; // finally! 
    }catch(...){ 
    continue; // until it works, dammit! 
    } 
} 

¡Incluso proporciona la fuerte excepción de garantía!


En una nota más seria, es como pisar una mina, a sabiendas de que se apagará si se mueve el pie ...

Y no es en realidad una forma de evitar el comportamiento indefinido de la doble destrucción aquí:

#include <cstdlib> 

T x(31, Blue, false); 
x.~T(); 
try{ 
    ::new (&x) T(22, Brown, true); 
}catch(...){ 
    std::exit(1); // doesn't call destructors of automatic objects 
} 
+1

Hmm ... ¡y la mina sería una clase que se lanza a todas las llamadas de constructores, excepto a la primera! –

+0

Por supuesto, dependiendo de su tipo T, esto podría repetirse por toda la eternidad (si el constructor siempre lanza para esos argumentos), entonces es "seguro para ciertas definiciones de seguridad" (garantía fuerte o no, no estoy convencido de que me gustaría eso en mi código) – Grizzly

+0

Mi solución habitual era hacer un buffer alineado y una ubicación nueva para comenzar. Luego evitas la destrucción automática si la reconstrucción falla, evitando la trampa. –

9

Si la expresión de construcción de T arroja, se duplicará la destrucción del objeto, que es UB. Por supuesto, incluso el deseo de hacer esto es indicativo de una falla de diseño.

+0

¿Falla de diseño o falla moral? – Crashworks

+0

@Crashworks: Más bien, falla de autoestima. Ya sabes, comenzando de nuevo y tal ...;) – Xeo

+0

Una pregunta: parece que una llamada a ~ T() es especial, ya que (obviamente) llamará a cualquier código que haya puesto explícitamente en su destructor, y también ¿Haces mucha destrucción automática de cosas? Anteriormente, pensé que las cosas automáticas simplemente sucedían alrededor de "borrar" y el final del alcance? Algo nuevo aprendido. –

7

Intenté compilarlo, pero solo me atreví a ejecutarlo bajo depurador. Así que eché un vistazo al desmontaje de mi compilador anterior generado (los comentarios son del compilador también):

@1 sub nerve.cells, fa0h 
@2 xor x, x  // bitch. 
@3 mov out, x 
@4 test out, out 
@5 jne @1 
@6 xor x, x  // just in case. 
@7 sub money, 2BC // dammit. 
@8 mov %x, new.one 
@8 cmp new.one, %x 
@9 jne @7 
... 
@25 jmp @1  // sigh... 
2

Mmm. Ya que está haciendo todo lo que C++ desalienta, creo que todo el mundo se está olvidando de goto.

Tenga en cuenta que después de la explícita X.~T() llamada, y antes de que se reconstruye , todavía habría doble quebrantamiento si alguien hizo un goto que antes de la declaración/inicialización de la variable x (incluso dentro de la sección Cobertura del interior).

Como obviamente podrías documentar eso, no pasaré por la molestia de tratar de "arreglarlo". Podría, conceptualmente, diseñar una clase RAII para gestionar la reconstrucción de objetos en el lugar, haciendo que esta maniobra sea segura para los goto en cualquier lugar. Tenga en cuenta que puede hacer que la llamada de constructor de ubicación-nueva se reenvíe perfectamente desde el destructor del objeto administrador RAII. La vida es buena.

Las otras advertencias se siguen aplicando, por supuesto (ver otras respuestas)


podemos asumir nothrow constuction para este momento

0

No hay nada que le impida hacer esto, lo hará trabajo en la mayoría de los casos. Pero como es el caso en una gran cantidad de C++ sabiendo que los detalles de sus casos será la diferencia entre que funcione como desee y un volcado de núcleo. Hay muy pocos ejemplos de razones por las que podría ver por qué querría hacer esto en un programa real, el único que tiene sentido es un archivo mapeado en la memoria.

Cuestiones relacionadas