¿Por qué el destructor predeterminado de C++ no destruye mis objetos?

8 minutos de lectura

La especificación de C++ dice que el destructor predeterminado elimina todos los miembros no estáticos. Sin embargo, no puedo lograrlo.

Tengo esto:

class N {
public:
    ~N() {
        std::cout << "Destroying object of type N";
    }
};

class M {
public:
    M() {
        n = new N;
    }
//  ~M() { //this should happen by default
//      delete n;
//  }
private:
    N* n;
};

Entonces esto debería imprimir el mensaje dado, pero no lo hace:

M* m = new M();
delete m; //this should invoke the default destructor

  • Esta es la razón por la que se prefiere el almacenamiento automático al almacenamiento dinámico. Utilice la asignación dinámica solo cuando sea necesario.

    – GManNickG

    8 de marzo de 2010 a las 17:36

  • @GMan: De acuerdo, obviamente, pero Oszkar todavía necesita saber cómo y por qué funciona esto.

    – John Dibling

    8 de marzo de 2010 a las 17:45

  • @John: Y él sabe, por lo tanto, “es por eso que…”

    – GManNickG

    8 de marzo de 2010 a las 18:30

  • Definitivamente estoy de acuerdo en que el almacenamiento automático (u objetos del administrador de recursos, como tr1::shared_ptr) es el camino a seguir, pero no veo por qué esto no funciona de acuerdo con la especificación.

    – Oszkar

    9 de marzo de 2010 a las 11:09

  • n es un puntero. El puntero es destruido por el destructor predeterminado. que apunta a no lo es, esa es tu responsabilidad.

    – nos

    23 de agosto de 2010 a las 23:17


avatar de usuario
Fred Larson

¿Qué te hace pensar que el objeto n ¿Los puntos a deben eliminarse de forma predeterminada? El destructor predeterminado destruye el puntero, no lo que apunta.

Editar: Veré si puedo dejar esto un poco más claro.

Si tuviera un puntero local y se saliera del alcance, ¿esperaría que el objeto al que apunta fuera destruido?

{
    Thing* t = new Thing;

    // do some stuff here

    // no "delete t;"
}

los t el puntero se limpia, pero el Thing apunta a que no lo es. Esta es una fuga. Esencialmente lo mismo está sucediendo en tu clase.

  • @OP: Porque no tiene forma de saber que está bien hacer eso. Podrías tener un puntero a algo que no debería ser destruido (de hecho, será bastante común que ese sea el caso).

    –TJ Crowder

    8 de marzo de 2010 a las 17:01


  • Además, incluso si agrega el bit comentado, es posible que deba vaciar la transmisión antes de ver la salida.

    – jk.

    8 de marzo de 2010 a las 17:01

  • @TJ Crowder: pero aún así: ¿no es destruir el objeto lo que requiere la especificación? @jk: ¿por qué tendría que vaciar la corriente?

    – Oszkar

    9 de marzo de 2010 a las 11:10

  • @Oszkar: la especificación solo requiere que se destruyan objetos automáticos … En este caso, el puntero es destruido tan pronto como quede fuera del alcance. Pero que si some stuff transmitió el puntero a otro objeto/función que no está fuera del alcance? Si el Thing todavía ser destruido?

    – Rafael Saint-Pierre

    9 de marzo de 2010 a las 15:08


  • @Parham: Correcto. Usar el puntero después de que se haya destruido es un comportamiento indefinido. Podría funcionar bien, podría causar el colapso inmediato del universo, o cualquier cosa intermedia.

    – Fred Larson

    10 mayo 2015 a las 16:43

avatar de usuario
P Shved

Imagina algo como esto:

class M {
public:
    M() { }
//  ~M() {        // If this happens by default
//      delete n; // then this will delete an arbitrary pointer!
//  }
private:
    N* n;
};

Estás solo con punteros en C++. Nadie los eliminará automáticamente por ti.

De hecho, el destructor predeterminado destruirá todos los objetos miembros. Pero el objeto miembro en este caso es un puntero en sí mismo, no la cosa a la que apunta. Esto podría haberte confundido.

Sin embargo, si en lugar de un simple puntero incorporado, utiliza un puntero inteligente, la destrucción de dicho “puntero” (que en realidad es una clase) podría desencadenar la destrucción del objeto señalado.

  • Por eso existen los punteros inteligentes, para que haya destrucción automática.

    –David Thornley

    8 de marzo de 2010 a las 17:01

  • “El destructor predeterminado de hecho destruirá todos los objetos miembro. Pero el objeto miembro en este caso es un puntero en sí mismo, no lo que apunta. Esto podría haberlo confundido”. -> Efectivamente. Eso lo aclara. ¡Gracias! 🙂

    – Oszkar

    9 de marzo de 2010 a las 11:21

  • @Oszkar: Creo que puedes cancelar la aceptación de una respuesta y luego aceptar otra. ¡Parece que éste es más apropiado!

    – Rafael Saint-Pierre

    9 de marzo de 2010 a las 15:11

  • @RaphaelSP: Obtuve varias respuestas apropiadas (¡y muy buenas!) y decidí marcar la primera porque era difícil decidir.

    – Oszkar

    10 de marzo de 2010 a las 11:09

avatar de usuario
david thornley

El destructor predeterminado está destruyendo el puntero. Si desea eliminar el N con Mdestructor por defecto, use un puntero inteligente. Cambio N * n; a auto_ptr<N> n; y n será destruido.

Editar: como se señaló en los comentarios, auto_ptr<> no es el mejor puntero inteligente para todos los usos, pero parece lo que se pide aquí. Representa específicamente la propiedad: la N en una M está allí durante la duración de la M y ya no. Copiar o asignar un auto_ptr<> representa un cambio en la propiedad, que por lo general no es lo que desea. Si desea pasar un puntero de M, debe pasar un N * obtenido de n.get().

Una solución más general sería boost::shared_ptr<>, que estará en el estándar C++0x. Eso se puede usar prácticamente donde se usaría un puntero sin formato. No es la construcción más eficiente y tiene problemas con las referencias circulares, pero generalmente es una construcción segura.

Otra edición: para responder a la pregunta en otro comentario, el comportamiento estándar del destructor predeterminado es destruir todos los miembros de datos y clases base. Sin embargo, eliminar un puntero sin procesar simplemente elimina el puntero, no lo que apunta. Después de todo, la implementación no puede saber si ese es el único puntero, o el importante, o algo por el estilo. La idea detrás de los punteros inteligentes es que eliminar un puntero inteligente al menos conducirá a la eliminación de lo que apunta, que suele ser el comportamiento deseado.

  • Realmente debe asegurarse de comprender lo que está sucediendo antes de usar auto_ptr. Su semántica de copia y asignación puede no ser la esperada.

    –Scott Wolchok

    8 de marzo de 2010 a las 18:52

  • @David: entiendo que mi ejemplo no es una solución de diseño inteligente. Solo estoy tratando de descubrir el comportamiento estándar de los destructores predeterminados. Usaría tr1::shared_ptr (ya está incluido en gcc) o simplemente almacenaría el objeto por valor. Perdón por no ser lo suficientemente específico con esta pregunta.

    – Oszkar

    9 de marzo de 2010 a las 11:13


  • @Oszkar: Sí, y el comportamiento estándar es eliminar los elementos y las clases base. Lamento no haberlo dejado más claro. Editaré de nuevo.

    –David Thornley

    9 de marzo de 2010 a las 15:11

¿Hay alguna razón por la que use un puntero cuando el objeto apuntado parece pertenecer al objeto contenido? Simplemente almacene el objeto por valor:

class M
{
    N n;

public:

    M() : n()
    {
    }
};

Es incorrecto decir que el destructor elimina miembros Invoca al destructor de cada miembro (y clase base), lo que para tipos incorporados (como punteros) significa no hacer nada.

Pareo nuevoestá con Eliminars es su responsabilidad (ya sea manualmente o con la ayuda de punteros inteligentes).

Su argumento puede parecer sólido, pero no es así como funcionan las cosas para los punteros.

n en realidad está siendo destruido pero, lo que esto significa es que el N* se llama destructor, que NO destruye ningún objeto n está apuntando. Pensar en N*el destructor como si fuera un intdestructor de Elimina su valor, lo mismo sucede con un puntero, elimina la dirección a la que apunta, pero no necesita eliminar cualquier objeto que se encuentre en la dirección que acaba de eliminar.

avatar de usuario
felipe alfarero

Creo que puede estar confundido acerca de los niveles de direccionamiento indirecto aquí. Cuando se destruye una instancia, cada miembro de datos se destruye junto con ella. En su caso, cuando un M es destruido y M::~M() se llama, su variable n realmente se destruye. El problema es ese n es un N *por lo que mientras el puntero se destruye, la cosa a la que apunta no lo es.

delete no funciona así. Considere su declaración simple:

delete n;

La declaración anterior destruye la cosa que n apunta a, que es un objeto de tipo N. no destruye n mismo, que es un N * puntero.

Hay una muy buena razón por la que M::~M() no llama automaticamente delete n; que es esto: el N el objeto al que se hace referencia puede ser compartido entre varios M objetos, y si uno M fueron destruidos, el resto perdería el N estaban señalando, dejando horribles punteros colgando por todas partes. C++ no intenta interpretar lo que quiso decir hacer con sus punteros, solo hace lo que usted dicho que hacer

En breve, M realmente está destruyendo a todos sus miembros cuando se destruye, es solo que esta destrucción no hace lo que crees que debería hacer. Si desea un tipo de puntero que tome posesión de un objeto y lo destruya cuando se destruye el puntero, mire std::auto_ptr.

¿Ha sido útil esta solución?