¿Está permitido eliminar esto?

232

¿Está permitido delete this;si la instrucción delete es la última instrucción que se ejecutará en esa instancia de la clase? Por supuesto, estoy seguro de que el objeto representado por el thispuntero está newcreado.

Estoy pensando en algo como esto:

void SomeModule::doStuff()
{
    // in the controller, "this" object of SomeModule is the "current module"
    // now, if I want to switch over to a new Module, eg:

    controller->setWorkingModule(new OtherModule());

    // since the new "OtherModule" object will take the lead, 
    // I want to get rid of this "SomeModule" object:

    delete this;
}

¿Puedo hacer esto?

Martijn Courteaux
fuente
13
El principal problema sería que si delete thisha creado un acoplamiento estrecho entre la clase y el método de asignación utilizado para crear objetos de esa clase. Ese es un diseño de OO muy pobre, ya que lo más fundamental en OOP es hacer clases autónomas que no saben o no se preocupan por lo que está haciendo la persona que llama. Por lo tanto, una clase diseñada adecuadamente no debería saber o importar cómo se asignó. Si por alguna razón necesita un mecanismo tan peculiar, creo que un mejor diseño sería usar una clase de envoltura alrededor de la clase real y dejar que la envoltura se encargue de la asignación.
Lundin
¿No puedes borrar setWorkingModule?
Jimmy T.

Respuestas:

238

C ++ FAQ Lite tiene una entrada específica para esto

Creo que esta cita lo resume muy bien

Mientras tenga cuidado, está bien que un objeto se suicide (elimine esto).

JaredPar
fuente
15
El FQA correspondiente también tiene algunos comentarios útiles: yosefk.com/c++fqa/heap.html#fqa-16.15
Alexandre C.
1
Por seguridad, puede usar un destructor privado en el objeto original para asegurarse de que no esté construido en la pila o como parte de una matriz o vector.
Cem Kalyoncu
Definir 'cuidado'
CinCout
3
'Cuidado' se define en el artículo de preguntas frecuentes vinculado. (Si bien el enlace FQA se queja principalmente, como casi todo lo que
contiene
88

Sí, delete this;tiene resultados definidos, siempre y cuando (como haya notado) se asegure de que el objeto se asignó dinámicamente y (por supuesto) nunca intente usar el objeto después de que se destruya. A lo largo de los años, se han formulado muchas preguntas sobre lo que dice el estándar específicamente delete this;, en lugar de eliminar algún otro puntero. La respuesta a eso es bastante corta y simple: no dice mucho de nada. Simplemente dice que deleteel operando debe ser una expresión que designe un puntero a un objeto, o una matriz de objetos. Entra en bastante detalle sobre cosas como cómo se da cuenta de qué función de desasignación (si la hay) llamar para liberar la memoria, pero toda la sección sobre delete(§ [expr.delete]) no menciona delete this;específicamente nada. La sección sobre destructores mencionadelete this en un lugar (§ [class.dtor] / 13):

En el punto de definición de un destructor virtual (incluida una definición implícita (15.8)), la función de desasignación sin matriz se determina como si la expresión eliminara esta aparición en un destructor no virtual de la clase del destructor (ver 8.3.5 )

Eso tiende a respaldar la idea de que el estándar considera delete this;válido: si no fuera válido, su tipo no sería significativo. Ese es el único lugar que el estándar menciona delete this;, hasta donde yo sé.

De todos modos, algunos consideran delete thisun truco desagradable y le dicen a cualquiera que escuche que debe evitarse. Un problema comúnmente citado es la dificultad de garantizar que los objetos de la clase solo se asignen dinámicamente. Otros lo consideran un idioma perfectamente razonable, y lo usan todo el tiempo. Personalmente, estoy en un punto intermedio: rara vez lo uso, pero no dudes en hacerlo cuando parezca ser la herramienta adecuada para el trabajo.

La primera vez que usa esta técnica es con un objeto que tiene una vida que es casi completamente propia. Un ejemplo que James Kanze citó fue un sistema de facturación / seguimiento en el que trabajó para una compañía telefónica. Cuando empiezas a hacer una llamada telefónica, algo toma nota de eso y crea un phone_callobjeto. A partir de ese momento, el phone_callobjeto maneja los detalles de la llamada telefónica (hacer una conexión cuando marca, agregar una entrada a la base de datos para decir cuándo comenzó la llamada, posiblemente conectar a más personas si hace una llamada de conferencia, etc.) Cuando las últimas personas en la llamada cuelgan, el phone_callobjeto realiza su contabilidad final (por ejemplo, agrega una entrada a la base de datos para decir cuándo colgó, para que puedan calcular cuánto tiempo duró su llamada) y luego se destruye a sí mismo. La vida de laphone_callEl objeto se basa en cuándo la primera persona inicia la llamada y cuándo las últimas personas abandonan la llamada, desde el punto de vista del resto del sistema, es básicamente completamente arbitrario, por lo que no puede vincularlo a ningún ámbito léxico en el código , o cualquier cosa en ese orden.

Para cualquier persona a la que le importe cuán confiable puede ser este tipo de codificación: si realiza una llamada telefónica a, desde o a través de casi cualquier parte de Europa, hay muchas posibilidades de que se maneje (al menos en parte) mediante código eso hace exactamente esto.

Jerry Coffin
fuente
2
Gracias, lo pondré en algún lugar de mi memoria. Supongo que define los constructores y destructores como privados y utiliza algún método de fábrica estático para crear dichos objetos.
Alexandre C.
@Alexandre: Probablemente harías eso en la mayoría de los casos de todos modos, no conozco ningún lugar cercano a todos los detalles del sistema en el que estaba trabajando, así que no puedo decirlo con certeza.
Jerry Coffin
La forma en que eludí el problema de cómo se asignó la memoria es incluir un bool selfDeleteparámetro en el constructor que se asigna a una variable miembro. De acuerdo, esto significa entregarle al programador suficiente cuerda para atar una soga, pero creo que eso es preferible a las pérdidas de memoria.
MBraedley
1
@MBraedley: He hecho lo mismo, pero prefiero evitar lo que me parece un error.
Jerry Coffin
Para cualquiera a quien le importe ... hay una posibilidad bastante buena de que esté siendo manejado (al menos en parte) por un código que lo haga exactamente this. Sí, el código está siendo manejado por exactamente this. ;)
Galaxy
46

Si te asusta, hay un truco perfectamente legal:

void myclass::delete_me()
{
    std::unique_ptr<myclass> bye_bye(this);
}

Sin delete thisembargo, creo que es idiomático C ++, y solo lo presento como una curiosidad.

Hay un caso en el que esta construcción es realmente útil: puede eliminar el objeto después de lanzar una excepción que necesita datos del miembro del objeto. El objeto permanece válido hasta después del lanzamiento.

void myclass::throw_error()
{
    std::unique_ptr<myclass> bye_bye(this);
    throw std::runtime_exception(this->error_msg);
}

Nota: si está utilizando un compilador anterior a C ++ 11 que puede usar en std::auto_ptrlugar de std::unique_ptr, hará lo mismo.

Mark Ransom
fuente
No puedo hacer que esto se compile usando c ++ 11, ¿hay algunas opciones especiales de compilación para ello? Además, ¿no requiere un movimiento del puntero de este?
Búho
@Owl no estoy seguro de lo que quieres decir, funciona para mí: ideone.com/aavQUK . Crear un unique_ptrdesde otro unique_ptr requiere un movimiento, pero no desde un puntero sin formato. ¿A menos que las cosas cambien en C ++ 17?
Mark Ransom
Ahh C ++ 14, esa será la razón. Necesito actualizar mi c ++ en mi caja de desarrollo. ¡Intentaré nuevamente esta noche en mi sistema gentoo recientemente creado!
Owl
25

Una de las razones por las que C ++ fue diseñado fue para facilitar la reutilización del código. En general, C ++ debe escribirse para que funcione si la clase se instancia en el montón, en una matriz o en la pila. "Eliminar esto" es una práctica de codificación muy mala porque solo funcionará si se define una única instancia en el montón; y es mejor que no haya otra declaración de eliminación, que generalmente utilizan la mayoría de los desarrolladores para limpiar el montón. Hacer esto también supone que ningún programador de mantenimiento en el futuro curará una pérdida de memoria falsamente percibida al agregar una declaración de eliminación.

Incluso si sabe de antemano que su plan actual es asignar solo una instancia en el montón, ¿qué pasa si algún desarrollador feliz aparece en el futuro y decide crear una instancia en la pila? O, ¿qué pasa si corta y pega ciertas partes de la clase a una nueva clase que tiene la intención de usar en la pila? Cuando el código llegue a "eliminar esto", se apagará y lo eliminará, pero luego, cuando el objeto salga del alcance, llamará al destructor. Luego, el destructor intentará eliminarlo nuevamente y luego se le aplicará una manguera. En el pasado, hacer algo así arruinaría no solo el programa sino también el sistema operativo y la computadora. En cualquier caso, esto NO es muy recomendable y casi siempre debe evitarse. Tendría que estar desesperado, seriamente enyesado,

Bob Bryan
fuente
77
+1. No puedo entender por qué te rechazaron. "C ++ debe escribirse para que funcione si la clase se instancia en el montón, en una matriz o en la pila" es un muy buen consejo.
Joh
1
Simplemente puede envolver el objeto que desea eliminar en una clase especial que elimina el objeto y luego a sí mismo, y utilizar esta técnica para evitar la asignación de la pila: stackoverflow.com/questions/124880/... Hay momentos en que realmente no hay un alternativa viable. Acabo de utilizar esta técnica para eliminar automáticamente un subproceso iniciado por una función DLL, pero la función DLL debe volver antes de que finalice el subproceso.
Felix Dombek
No se puede programar de tal manera que alguien que solo copia y pegue su código termine abusándolo de todos modos
Jimmy T.
22

Está permitido (simplemente no use el objeto después de eso), pero no escribiría ese código en la práctica. Creo que delete thisdebería aparecer sólo en las funciones que llama releaseo Releasey se parece a: void release() { ref--; if (ref<1) delete this; }.

Kirill V. Lyadvinsky
fuente
Que es exactamente una vez en cada proyecto mío ... :-)
cmaster - restablecer monica
15

Bueno, en la delete thisconstrucción del Modelo de objetos componentes (COM) puede ser una parte del Releasemétodo que se llama cada vez que desea liberar un objeto requerido:

void IMyInterface::Release()
{
    --instanceCount;
    if(instanceCount == 0)
        delete this;
}
Desconocido
fuente
8

Este es el idioma central para los objetos contados por referencia.

El recuento de referencias es una forma sólida de recolección de basura determinista: garantiza que los objetos administren su PROPIA vida útil en lugar de depender de punteros 'inteligentes', etc. para hacerlo por ellos. Solo se accede al objeto subyacente a través de punteros inteligentes "Referencia", diseñados para que los punteros incrementen y disminuyan un entero miembro (el recuento de referencia) en el objeto real.

Cuando la última referencia cae de la pila o se elimina, el recuento de referencia irá a cero. El comportamiento predeterminado de su objeto será una llamada para "eliminar esto" a la recolección de basura: las bibliotecas que escribo proporcionan una llamada virtual protegida "CountIsZero" en la clase base para que pueda anular este comportamiento para cosas como el almacenamiento en caché.

La clave para hacer esto seguro es no permitir a los usuarios acceder al CONSTRUCTOR del objeto en cuestión (protegerlo), sino hacer que llamen a algún miembro estático, la FÁBRICA, como "Creación de referencia estática (...)". De esa manera, SABE con certeza que siempre están construidos con "nuevo" ordinario y que no hay ningún puntero sin formato disponible, por lo que "eliminar esto" nunca explotará.

Zack Yezek
fuente
¿Por qué no puede simplemente tener un "asignador / recolector de basura" de clase (singleton), una interfaz a través de la cual se realiza toda la asignación y dejar que esa clase maneje todo el recuento de referencias de los objetos asignados? En lugar de obligar a los objetos a molestarse con las tareas de recolección de basura, algo que no tiene relación alguna con su propósito designado.
Lundin
1
También puede proteger el destructor para prohibir las asignaciones estáticas y de pila de su objeto.
Game_Overture
7

Puedes hacerlo Sin embargo, no puede asignar a esto. Por lo tanto, la razón por la que declaras hacer esto, "Quiero cambiar la vista", parece muy cuestionable. El mejor método, en mi opinión, sería que el objeto que contiene la vista reemplace esa vista.

Por supuesto, está utilizando objetos RAII y, por lo tanto, no necesita llamar a eliminar en absoluto ... ¿verdad?

Edward extraño
fuente
4

Esta es una vieja pregunta contestada, pero @Alexandre preguntó "¿Por qué alguien querría hacer esto?", Y pensé que podría proporcionar un ejemplo de uso que estoy considerando esta tarde.

Código heredado Utiliza punteros desnudos Obj * obj con un obj de eliminación al final.

Lamentablemente, a veces necesito mantener el objeto vivo durante más tiempo.

Estoy considerando convertirlo en un puntero inteligente de referencia. Pero habría mucho código para cambiar, si tuviera que usarref_cnt_ptr<Obj> todas partes. Y si mezcla Obj * desnudo y ref_cnt_ptr, puede eliminar el objeto implícitamente cuando desaparezca el último ref_cnt_ptr, aunque todavía haya Obj * vivo.

Así que estoy pensando en crear un explícito_delete_ref_cnt_ptr. Es decir, un puntero contado de referencia donde la eliminación solo se realiza en una rutina de eliminación explícita. Utilizándolo en el único lugar donde el código existente conoce la vida útil del objeto, así como en mi nuevo código que mantiene el objeto vivo por más tiempo.

Aumentando y decrementando el recuento de referencias a medida que se manipula explícitamente_delete_ref_cnt_ptr.

Pero NO se libera cuando el recuento de referencias se ve como cero en el destructor explicit_delete_ref_cnt_ptr.

Solo se libera cuando se considera que el recuento de referencia es cero en una operación explícita de eliminación. Por ejemplo, en algo como:

template<typename T> class explicit_delete_ref_cnt_ptr { 
 private: 
   T* ptr;
   int rc;
   ...
 public: 
   void delete_if_rc0() {
      if( this->ptr ) {
        this->rc--;
        if( this->rc == 0 ) {
           delete this->ptr;
        }
        this->ptr = 0;
      }
    }
 };

De acuerdo, algo así. Es un poco inusual tener un tipo de puntero contado de referencia que no elimine automáticamente el objeto señalado en el destructor ptr rc'ed. Pero parece que esto podría hacer que mezclar punteros desnudos y punteros rojos sea un poco más seguro.

Pero hasta ahora no es necesario eliminar esto.

Pero entonces se me ocurrió: si el objeto apuntado, el puntero, sabe que se está contando por referencia, por ejemplo, si el recuento está dentro del objeto (o en alguna otra tabla), entonces la rutina delete_if_rc0 podría ser un método de objeto de puntero, no el puntero (inteligente).

class Pointee { 
 private: 
   int rc;
   ...
 public: 
   void delete_if_rc0() {
        this->rc--;
        if( this->rc == 0 ) {
           delete this;
        }
      }
    }
 };

En realidad, no necesita ser un método miembro, pero podría ser una función libre:

map<void*,int> keepalive_map;
template<typename T>
void delete_if_rc0(T*ptr) {
        void* tptr = (void*)ptr;
        if( keepalive_map[tptr] == 1 ) {
           delete ptr;
        }
};

(Por cierto, sé que el código no es del todo correcto; se vuelve menos legible si agrego todos los detalles, así que lo dejo así).

Krazy Glew
fuente
0

Eliminar esto es legal siempre que el objeto esté en el montón. Debería requerir que el objeto sea solo un montón. La única forma de hacerlo es proteger el destructor; de esta manera, eliminar se puede llamar SOLO desde la clase, por lo que necesitaría un método que garantice la eliminación

Swift - Friday Pie
fuente