dynamic memory c++ new
¿Se permite eliminar esto? (10)
¿Está permitido delete this;
¿Si la declaración de eliminación es la última instrucción que se ejecutará en esa instancia de la clase? Por supuesto, estoy seguro de que el objeto representado por this
puntero está new
creado.
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?
Bueno, en Component Object Model (COM), delete this
construcción puede ser una parte del método de Release
que se llama cuando se quiere liberar un objeto adquirido:
void IMyInterface::Release()
{
--instanceCount;
if(instanceCount == 0)
delete this;
}
El C ++ FAQ Lite tiene una entrada específicamente para esto
Creo que esta cita lo resume muy bien
Mientras tenga cuidado, está bien que un objeto se suicide (elimine esto).
Eliminar esto es legal siempre que el objeto esté en el montón. Necesitarías requerir que el objeto sea solo de pila. La única forma de hacerlo es hacer que el destructor esté protegido: de esta manera, la eliminación se puede llamar SOLO desde la clase, por lo que necesitará un método que garantice la eliminación
Está permitido (simplemente no usar el objeto después de eso), pero no escribiría tal código en la práctica. Creo que delete this
debería aparecer solo en las funciones que llamaron release
o Release
y se parece a: void release() { ref--; if (ref<1) delete this; }
void release() { ref--; if (ref<1) delete this; }
void release() { ref--; if (ref<1) delete this; }
.
Esta es una pregunta antigua, 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 una eliminación obj al final.
Desafortunadamente, a veces, no a menudo, necesito mantener vivo el objeto por más tiempo.
Estoy considerando convertirlo en un puntero inteligente de referencia contado. Pero habría un montón de código para cambiar, si tuviera que usar ref_cnt_ptr<Obj>
todas partes. Y si mezcla Obj * desnudo y ref_cnt_ptr, puede eliminar el objeto implícitamente cuando desaparece el último ref_cnt_ptr, aunque haya Obj * vivo.
Así que estoy pensando en crear un explicit_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. Usándolo en el 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.
Incrementando y disminuyendo el recuento de referencia como se explícita_delete_ref_cnt_ptr se manipula.
Pero NO se libera cuando se ve que el recuento de referencia es cero en el destructor explicit_delete_ref_cnt_ptr.
Solo se libera cuando se ve que el recuento de referencia es cero en una operación de eliminación explícita. 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;
}
}
};
OK, algo así. Es un poco inusual tener un tipo de puntero contado de referencia que no elimine automáticamente el objeto apuntado en el destructor de ptr de rc''ed. Pero parece que esto podría hacer que la mezcla de punteros desnudos y punteros rc''ed sea un poco más segura.
Pero hasta ahora no hay necesidad de borrar esto.
Pero entonces se me ocurrió: si el objeto señalado, el titular, sabe que se está contando como referencia, por ejemplo, si el recuento está dentro del objeto (o en alguna otra tabla), la rutina delete_if_rc0 podría ser un método de objeto pointee, 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 en absoluto, pero podría ser una función gratuita:
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 correcto; se vuelve menos legible si agrego todos los detalles, así que lo dejo así).
Este es el lenguaje central para los objetos contados de referencia.
El recuento de referencias es una forma sólida de recolección de basura determinista: garantiza que los objetos gestionen su PROPIA vida útil en lugar de depender de punteros "inteligentes", etc. para que lo hagan por ellos. Solo se puede acceder al objeto subyacente a través de punteros inteligentes de "Referencia", diseñados para que los punteros incrementen y disminuyan un número entero miembro (el recuento de referencia) en el objeto real.
Cuando la última referencia se cae de la pila o se elimina, el recuento de referencia se pondrá a cero. El comportamiento predeterminado de su objeto será una llamada a "eliminar esto" para recolectar basura. Las bibliotecas que escribo proporcionan una llamada "CountIsZero" virtual protegida en la clase base para que pueda anular este comportamiento para cosas como el almacenamiento en caché.
La clave para hacer que esto sea seguro no es permitir que los usuarios accedan al CONSTRUCTOR del objeto en cuestión (hacer que esté protegido), sino a hacer que llamen a algún miembro estático, el "Estático de Referencia Crear T (...)", como FACTORY. De esa manera, SABES con certeza que siempre se construyen con un "nuevo" ordinario y que nunca hay un puntero en bruto disponible, por lo que "eliminar esto" nunca explotará.
Sí, delete this;
ha definido los resultados, siempre y cuando (como lo haya indicado) asegure que el objeto se asignó dinámicamente y (por supuesto) nunca intente usar el objeto después de que se haya destruido. A lo largo de los años, se han formulado muchas preguntas sobre lo que dice la norma específicamente sobre cómo delete this;
, a diferencia de borrar algún otro puntero. La respuesta es bastante breve y simple: no dice mucho de nada. Simplemente dice que el operando de delete
debe ser una expresión que designe un puntero a un objeto, o una matriz de objetos. Entra en un poco de detalle acerca de cosas como la forma de averiguar qué función de desasignación (si existe) llamar para liberar la memoria, pero la sección completa sobre la delete
(§ [expr.delete]) no menciona la delete this;
específicamente a todos La sección sobre destructores menciona delete 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 no de matriz se determina como si para la expresión eliminar aparece en un destructor no virtual de la clase del destructor (ver 8.3.5 ).
Eso tiende a apoyar la idea de que la norma considera delete this;
Para ser válido, si fuera inválido, su tipo no tendría sentido. Ese es el único lugar donde las menciones estándar delete this;
en absoluto, por lo que yo sé.
De todos modos, algunos consideran delete this
un truco desagradable, y decirle a cualquiera que escuche que se debe evitar. Un problema comúnmente citado es la dificultad de garantizar que los objetos de la clase solo se asignen de forma dinámica. 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 dude en hacerlo cuando parece ser la herramienta adecuada para el trabajo.
La primera vez que utiliza esta técnica es con un objeto que tiene una vida que es casi totalmente propia. Un ejemplo que James Kanze ha citado es un sistema de facturación / seguimiento en el que trabajó para una compañía telefónica. Cuando comienza a hacer una llamada telefónica, algo toma nota de eso y crea un objeto phone_call
. A partir de ese momento, el objeto phone_call
maneja los detalles de la llamada telefónica (establecer una conexión al marcar, agregar una entrada a la base de datos para indicar cuándo comenzó la llamada, posiblemente conectar a más personas si realiza una llamada de conferencia, etc.) Cuando las últimas personas en la llamada cuelgan, el objeto phone_call
realiza su contabilidad final (por ejemplo, agrega una entrada a la base de datos para decir cuándo colgó, para que puedan calcular el tiempo de su llamada) y luego se destruye. La vida útil del objeto phone_call
se basa en cuando la primera persona comienza la llamada y cuando la última persona deja la llamada: desde el punto de vista del resto del sistema, es básicamente completamente arbitrario, por lo que no puede vincularla a ninguna. alcance léxico en el código, o cualquier cosa en ese orden.
Para cualquier persona que se preocupe por la fiabilidad de este tipo de codificación: si realiza una llamada telefónica a, desde o desde casi cualquier parte de Europa, existe una gran posibilidad de que sea manejada (al menos en parte) por código Eso hace exactamente esto.
Si te asusta, hay un hack perfectamente legal:
void myclass::delete_me()
{
std::unique_ptr<myclass> bye_bye(this);
}
Sin embargo, creo que delete this
es idiomático en C ++, y solo lo presento como una curiosidad.
Existe 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 de miembros 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á usando un compilador más antiguo que C ++ 11, puede usar std::auto_ptr
lugar de std::unique_ptr
, hará lo mismo.
Una de las razones por las que se diseñó C ++ fue para facilitar la reutilización del código. En general, C ++ debe escribirse para que funcione si la clase se crea una 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 suele ser utilizada por la mayoría de los desarrolladores para limpiar el montón. Al hacer esto, también se supone que ningún programador de mantenimiento en el futuro solucionará una pérdida de memoria percibida falsamente 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é pasaría si algún desarrollador feliz viene 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 pretende usar en la pila? Cuando el código llegue a "eliminar esto", se apagará y se borrará, pero cuando el objeto salga de su alcance, llamará al destructor. El destructor intentará eliminarlo de nuevo y, a continuación, quedará hosed. En el pasado, hacer algo como esto arruinaría no solo el programa, sino también el sistema operativo y la computadora tendrían que reiniciarse. En cualquier caso, esto NO es altamente recomendado y casi siempre debe evitarse. Tendría que estar desesperado, seriamente enlucido o realmente odiar a la compañía para la que trabajé para escribir el código que hizo esto.
Usted puede hacerlo Sin embargo, no se puede asignar a esto. Por lo tanto, la razón por la que declara hacer esto, "quiero cambiar la vista", parece muy cuestionable. El mejor método, en mi opinión, sería que el objeto que tiene la vista reemplace esa vista.
Por supuesto, estás usando objetos RAII y, por lo tanto, no necesitas llamar a eliminar ... ¿verdad?