¿Llamar a destructor manualmente siempre es un signo de mal diseño?

8 minutos de lectura

¿Llamar a destructor manualmente siempre es un signo de mal
Jirafa violeta

Estaba pensando: dicen que si estás llamando a destructor manualmente, estás haciendo algo mal. Pero, ¿es siempre así? ¿Hay algún contraejemplo? ¿Situaciones en las que es necesario llamarlo manualmente o en las que es difícil/imposible/poco práctico evitarlo?

  • ¿Cómo va a desasignar el objeto después de llamar al dtor, sin volver a llamarlo?

    – ssube

    06/01/2013 a las 21:36

  • @peachykeen: llamarías colocación new para inicializar un nuevo objeto en lugar del antiguo. En general, no es una buena idea, pero no es inaudito.

    – D. Shawley

    6 de enero de 2013 a las 21:38

  • Mire las “reglas” que contienen las palabras “siempre” y “nunca” que no provienen directamente de las especificaciones con sospecha: en la mayoría de los casos, quien las está enseñando quiere ocultarle cosas que debe saber, pero no lo hace. saber enseñar. Como un adulto respondiendo a un niño a una pregunta sobre sexo.

    –Emilio Garavaglia

    6 de enero de 2013 a las 22:24


  • Creo que está bien en caso de manipular con la construcción de objetos con técnica de colocación. stroustrup.com/bs_faq2.html#placement-delete (pero es algo de nivel bastante bajo y solo se usa cuando optimiza su software incluso en ese nivel)

    – Konstantin Burlachenko

    21 de agosto de 2018 a las 14:40


1646753414 768 ¿Llamar a destructor manualmente siempre es un signo de mal
Emilio Garavaglia

Todas las respuestas describen casos específicos, pero hay una respuesta general:

Llamas al dtor explícitamente cada vez que necesitas simplemente destruir el objeto (en el sentido de C++) sin soltar el memoria el objeto reside en.

Esto suele ocurrir en todas las situaciones en las que la asignación/desasignación de memoria se gestiona independientemente de la construcción/destrucción de objetos. En esos casos, la construcción se realiza a través de colocación nueva sobre un trozo de memoria existente, y la destrucción ocurre a través de una llamada dtor explícita.

Aquí está el ejemplo crudo:

{
  char buffer[sizeof(MyClass)];

  {
     MyClass* p = new(buffer)MyClass;
     p->dosomething();
     p->~MyClass();
  }
  {
     MyClass* p = new(buffer)MyClass;
     p->dosomething();
     p->~MyClass();
  }

}

Otro ejemplo notable es el predeterminado std::allocator cuando es usado por std::vector: los elementos se construyen en vector durante push_back, pero la memoria se asigna en fragmentos, por lo que existe antes de la construcción del elemento. Y por lo tanto, vector::erase debe destruir los elementos, pero no necesariamente desasigna la memoria (especialmente si tiene que ocurrir un nuevo push_back pronto…).

Es “mal diseño” en sentido estricto de programación orientada a objetos (debe administrar objetos, no memoria: el hecho de que los objetos requieran memoria es un “incidente”), es “buen diseño” en “programación de bajo nivel”, o en casos donde la memoria es no tomado de la “tienda gratuita” el valor predeterminado operator new compra

Es un mal diseño si sucede aleatoriamente alrededor del código, es un buen diseño si sucede localmente en clases específicamente diseñadas para ese propósito.

  • Solo curiosidad por saber por qué esta no es la respuesta aceptada.

    – Francis Cugler

    13 de marzo de 2018 a las 10:18

¿Llamar a destructor manualmente siempre es un signo de mal
Dietmar Kühl

Se requiere llamar al destructor manualmente si el objeto se construyó utilizando una forma sobrecargada de operator new()excepto cuando se utiliza el “std::nothrow” sobrecargas:

T* t0 = new(std::nothrow) T();
delete t0; // OK: std::nothrow overload

void* buffer = malloc(sizeof(T));
T* t1 = new(buffer) T();
t1->~T(); // required: delete t1 would be wrong
free(buffer);

Sin embargo, fuera de la gestión de la memoria en un nivel bastante bajo como el anterior, llamando a los destructores explícitamente, es un signo de mal diseño. Probablemente, en realidad no es solo un mal diseño, sino completamente incorrecto (sí, usar un destructor explícito seguido de una llamada de constructor de copia en el operador de asignación es un mal diseño y probablemente esté equivocado).

Con C++ 2011 hay otra razón para usar llamadas explícitas al destructor: cuando se usan uniones generalizadas, es necesario destruir explícitamente el objeto actual y crear un nuevo objeto usando la ubicación nueva al cambiar el tipo del objeto representado. Además, cuando se destruye la unión, es necesario llamar explícitamente al destructor del objeto actual si requiere destrucción.

  • En lugar de decir “utilizando una forma sobrecargada de operator new“, la frase correcta es “utilizando placement new“.

    – Rémy Lebeau

    6 de enero de 2013 a las 22:47

  • @RemyLebeau: Bueno, quería aclarar que no solo estoy hablando solo de operator new(std::size_t, void*) (y la variación de la matriz) sino más bien sobre todas las versiones sobrecargadas de operator new().

    – Dietmar Kühl

    6 de enero de 2013 a las 22:56

  • ¿Qué pasa cuando quieres copiar un objeto para hacer una operación en él sin alterarlo mientras la operación está computando? temp = Class(object); temp.operation(); object.~Class(); object = Class(temp); temp.~Class();

    – Jean-Luc Nacif Coelho

    23 de febrero de 2017 a las 2:20

  • yes, using an explicit destructor followed by a copy constructor call in the assignment operator is a bad design and likely to be wrong. ¿Por qué decís esto? Creo que si el destructor es trivial, o casi trivial, tiene una sobrecarga mínima y aumenta el uso del principio DRY. Si se usa en tales casos con un movimiento operator=(), incluso puede ser mejor que usar swap. YMMV.

    – Adrián

    3 abr 2018 a las 15:07


  • @Adrian: llamar al destructor y recrear el objeto cambia muy fácilmente el tipo del objeto: recreará un objeto con el tipo estático de la asignación, pero el tipo dinámico puede ser diferente. Eso es realmente un problema cuando la clase tiene virtual funciones (la virtual las funciones no se recrearán) y, de lo contrario, el objeto solo se [re-]construido.

    – Dietmar Kühl

    3 abr 2018 a las 21:19

No, no debe llamarlo explícitamente porque se llamaría dos veces. Una vez para la llamada manual y otra vez cuando termina el ámbito en el que se declara el objeto.

P.ej.

{
  Class c;
  c.~Class();
}

Si realmente necesita realizar las mismas operaciones, debe tener un método separado.

Hay un situación específica en el que es posible que desee llamar a un destructor en un objeto asignado dinámicamente con una ubicación new pero no parece algo que vayas a necesitar.

No, depende de la situación, a veces es legítimo y bien diseño.

Para comprender por qué y cuándo necesita llamar a los destructores explícitamente, veamos qué sucede con “nuevo” y “eliminar”.

Para crear un objeto dinámicamente, T* t = new T; debajo del capó: 1. se asigna tamaño de memoria (T). 2. Se llama al constructor de T para inicializar la memoria asignada. El operador new hace dos cosas: asignación e inicialización.

Para destruir el objeto delete t; bajo el capó: 1. Se llama destructor de T. 2. Se libera la memoria asignada para ese objeto. el operador delete también hace dos cosas: destrucción y desasignación.

Uno escribe el constructor para hacer la inicialización y el destructor para hacer la destrucción. Cuando llama explícitamente al destructor, solo se realiza la destrucción, pero no la desasignación.

Un uso legítimo de llamar explícitamente a destructor, por lo tanto, podría ser: “Solo quiero destruir el objeto, pero no (o no puedo) liberar la asignación de memoria (todavía)”.

Un ejemplo común de esto es la asignación previa de memoria para un grupo de ciertos objetos que, de lo contrario, deben asignarse dinámicamente.

Al crear un nuevo objeto, obtiene la porción de memoria del grupo preasignado y realiza una “ubicación nueva”. Después de terminar con el objeto, es posible que desee llamar explícitamente al destructor para finalizar el trabajo de limpieza, si corresponde. Pero en realidad no desasignará la memoria, como lo habría hecho el operador eliminar. En su lugar, devuelve el fragmento al grupo para su reutilización.

Como se cita en las preguntas frecuentes, debe llamar al destructor explícitamente cuando use la ubicación nueva.

Esta es la única vez que llama explícitamente a un destructor.

Sin embargo, estoy de acuerdo en que esto rara vez es necesario.

1646753417 829 ¿Llamar a destructor manualmente siempre es un signo de mal
james kanze

Cada vez que necesite separar la asignación de la inicialización, necesitará una ubicación nueva y una llamada explícita del destructor manualmente. Hoy en día, rara vez es necesario, ya que tenemos los contenedores estándar, pero si tiene que implementar algún nuevo tipo de contenedor, lo necesitará.

¿Llamar a destructor manualmente siempre es un signo de mal
marcinj

Hay casos en que son necesarios:

En el código en el que trabajo, uso una llamada de destructor explícita en los asignadores, tengo una implementación de asignador simple que usa la ubicación nueva para devolver bloques de memoria a contenedores stl. En destruir tengo:

  void destroy (pointer p) {
    // destroy objects by calling their destructor
    p->~T();
  }

mientras está en construcción:

  void construct (pointer p, const T& value) {
    // initialize memory with placement new
    #undef new
    ::new((PVOID)p) T(value);
  }

también se realiza la asignación en allocate() y la desasignación de memoria en desalocate(), utilizando mecanismos de asignación y desasignación específicos de la plataforma. Este asignador se usó para omitir doug lea malloc y usar directamente, por ejemplo, LocalAlloc en Windows.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad