significado - ¿Deberían los objetos eliminarse en C++?
tipos de datos en c++ (11)
Ciertamente funciona para objetos creados por new
y cuando la persona que llama de la Update
está debidamente informada sobre ese comportamiento. Pero lo evitaría. En su caso, la propiedad claramente está en el mundo, por lo que haría que el mundo lo elimine. El objeto no se crea a sí mismo, creo que tampoco debería eliminarse. Puede ser muy sorprendente si llama a una función "Actualizar" en su objeto, pero de repente ese objeto ya no existe sin que World haga nada por sí mismo (aparte de Quitarlo de su lista, ¡pero en otro marco! La actualización del objeto no se dará cuenta de eso).
Algunas ideas sobre esto
- Agregue una lista de referencias de objetos a World. Cada objeto en esa lista está pendiente de eliminación. Esta es una técnica común, y se usa en wxWidgets para ventanas de nivel superior que se cerraron, pero aún pueden recibir mensajes. En tiempo de inactividad, cuando se procesan todos los mensajes, se procesa la lista pendiente y se eliminan los objetos. Creo que Qt sigue una técnica similar.
- Dile al mundo que el objeto quiere ser eliminado. El mundo estará debidamente informado y se encargará de todo lo que se necesite hacer. Algo así como un
deleteObject(Object&)
tal vez. - Agregue una función
shouldBeDeleted
a cada objeto que devuelve verdadero si el objeto desea ser eliminado por su propietario.
Preferiría la opción 3. El mundo llamaría Actualizar. Y después de eso, busca si el objeto debe eliminarse y puede hacerlo, o si lo desea, recuerda ese hecho al agregar ese objeto a una lista de eliminación pendiente de forma manual.
Es un dolor en el culo cuando no puede estar seguro de cuándo y cuándo no poder acceder a las funciones y datos del objeto. Por ejemplo, en wxWidgets, hay una clase wxThread que puede operar en dos modos. Uno de estos modos (llamado "desmontable") es que si su función principal regresa (y los recursos de la secuencia deben liberarse), se elimina (para liberar la memoria ocupada por el objeto wxThread) en lugar de esperar al propietario del hilo objeto para llamar a una función de espera o unirse. Sin embargo, esto causa dolor de cabeza severo. Nunca puede llamar a ninguna función porque podría haberse cancelado en cualquier circunstancia, y no puede crearse con nueva. Mucha gente me dijo que no les gusta mucho su comportamiento.
La autodeclaración del objeto contado de referencia está oliendo, imho. Comparemos:
// bitmap owns the data. Bitmap keeps a pointer to BitmapData, which
// is shared by multiple Bitmap instances.
class Bitmap {
~Bitmap() {
if(bmp->dec_refcount() == 0) {
// count drops to zero => remove
// ref-counted object.
delete bmp;
}
}
BitmapData *bmp;
};
class BitmapData {
int dec_refcount();
int inc_refcount();
};
Compare eso con los objetos refinanciados autodestruibles:
class Bitmap {
~Bitmap() {
bmp->dec_refcount();
}
BitmapData *bmp;
};
class BitmapData {
int dec_refcount() {
int newCount = --count;
if(newCount == 0) {
delete this;
}
return newCount;
}
int inc_refcount();
};
Creo que el primero es mucho más agradable, y creo que los objetos contados de referencia bien diseñados no "eliminan esto", porque aumenta el acoplamiento: la clase que utiliza los datos contados de referencia tiene que saber y recordar que los datos se eliminan a sí mismos como efecto secundario de decremento del recuento de referencia. Observe cómo "bmp" se convierte posiblemente en un puntero colgante en ~ destructor de Bitmap. Podría decirse que no hacer eso "eliminar esto" es mucho mejor aquí.
Responda a una pregunta similar "¿Para qué sirve eliminar esto?"
Pasé los últimos 4 años en C #, así que estoy interesado en las mejores prácticas actuales y los patrones de diseño comunes en C ++. Considere el siguiente ejemplo parcial:
class World
{
public:
void Add(Object *object);
void Remove(Object *object);
void Update();
}
class Fire : Object
{
public:
virtual void Update()
{
if(age > burnTime)
{
world.Remove(this);
delete this;
}
}
}
Aquí tenemos un mundo responsable de administrar un conjunto de objetos y actualizarlos regularmente. El fuego es un objeto que podría agregarse al mundo bajo muchas circunstancias diferentes, pero típicamente por otro objeto que ya está en el mundo. El fuego es el único objeto que sabe cuándo se ha apagado, por lo que actualmente lo elimino. El objeto que creó el fuego probablemente ya no exista o no sea relevante.
¿Es esto sensato o hay un mejor diseño que ayude a limpiar estos objetos?
El problema con esto es que realmente estás creando un acoplamiento implícito entre el objeto y la clase mundial.
Si trato de llamar a Update () fuera de la clase mundial, ¿qué ocurre? Podría terminar con el objeto que se elimina, y no sé por qué. Parece que las responsabilidades están mal mezcladas. Esto causará problemas en el momento en que use la clase Fire en una situación nueva en la que no había pensado cuando escribió este código. ¿Qué ocurre si el objeto debe eliminarse de más de un lugar? Tal vez debería eliminarse tanto del mundo, el mapa actual y el inventario del jugador? La función Actualizar lo eliminará del mundo y luego eliminará el objeto, y la próxima vez que el mapa o el inventario intente acceder al objeto, sucederán cosas malas.
En general, diría que es muy poco intuitivo que una función Update () elimine el objeto que está actualizando. También diría que no es intuitivo que un objeto se elimine a sí mismo. El objeto debería tener más o menos una forma de disparar un evento diciendo que ha terminado de quemarse, y cualquier persona interesada puede actuar sobre eso. Por ejemplo sacándolo del mundo. Para eliminarlo, piense en términos de propiedad.
¿A quién pertenece el objeto? ¿El mundo? Eso significa que el mundo solo puede decidir cuándo muere el objeto. Eso está bien siempre y cuando la referencia del mundo al objeto vaya a durar más que otras referencias a él. ¿Crees que el objeto es en sí mismo? ¿Y eso que significa? El objeto debe ser eliminado cuando el objeto ya no existe? No tiene sentido.
Pero si no hay un único propietario claramente definido, implemente la propiedad compartida, por ejemplo, utilizando un puntero inteligente que implemente el recuento de referencias, como boost::shared_ptr
Pero tener una función de miembro en el objeto en sí, que está codificada para eliminar el objeto de una lista específica, si existe o no allí, y si también existe en cualquier otra lista, y también eliminar el objeto en sí, independientemente de qué las referencias a él existen, es una mala idea.
Esa es una implementación de recuento de referencia bastante común y lo he usado con éxito anteriormente.
Sin embargo, también lo he visto colapsar. Ojalá pudiera recordar las circunstancias del accidente. @abelenky es un lugar donde lo he visto @abelenky .
Pudo haber sido donde subclasificas Fire
, pero no creas un destructor virtual (mira a continuación). Cuando no tiene un destructor virtual, la función Update()
llamará ~Fire()
lugar del destructor apropiado ~Flame()
.
class Fire : Object
{
public:
virtual void Update()
{
if(age > burnTime)
{
world.Remove(this);
delete this; //Make sure this is a virtual destructor.
}
}
};
class Flame: public Fire
{
private:
int somevariable;
};
Esto no significa que haya algo intrínsecamente incorrecto en que un objeto se elimine a sí mismo, pero otro posible método sería hacer que el mundo sea responsable de eliminar objetos como parte de :: Eliminar (suponiendo que todos los objetos eliminados también se eliminaron).
Generalmente no es una buena idea hacer un "eliminar esto" a menos que sea necesario o que se use de una manera muy directa. En su caso, parece que World puede simplemente eliminar el objeto cuando se elimina, a menos que haya otras dependencias que no veamos (en cuyo caso su llamada de eliminación causará errores ya que no se notifican). Mantener la propiedad fuera de su objeto permite que su objeto esté mejor encapsulado y sea más estable: el objeto no se volverá inválido en algún momento simplemente porque elige eliminarse a sí mismo.
Ha hecho que Update
una función virtual, sugiriendo que las clases derivadas pueden anular la implementación de Update
. Esto introduce dos grandes riesgos.
1.) Una implementación anulada puede recordar hacer un World.Remove
, pero puede olvidarse de delete this
. La memoria se filtró.
2.) La implementación anulada llama a la Update
clase base, que hace una delete this
, pero luego continúa con más trabajo, pero con un puntero this-inválido.
Considera este ejemplo:
class WizardsFire: public Fire
{
public:
virtual void Update()
{
Fire::Update(); // May delete the this-object!
RegenerateMana(this.ManaCost); // this is now invaild! GPF!
}
}
La eliminación fallará y puede bloquear el programa si el objeto no fue asignado y construido con nuevo. Esta construcción le impedirá instanciar la clase en la pila o estáticamente. En su caso, parece estar utilizando algún tipo de esquema de asignación. Si te apegas a eso, esto podría ser seguro. Sin embargo, ciertamente no lo haría.
No hay nada realmente incorrecto con los objetos que se eliminan en C ++, la mayoría de las implementaciones de conteo de referencia usarán algo similar. Sin embargo, se ve como una técnica ligeramente esotérica, y tendrá que estar muy atento a los problemas de propiedad.
Otros han mencionado problemas con "eliminar esto". En resumen, dado que "Mundo" gestiona la creación de Fuego, también debe gestionar su eliminación.
Otro problema es si el mundo termina con un incendio aún encendido. Si este es el único mecanismo por el cual se puede destruir un incendio, entonces podrías terminar con un incendio huérfano o de basura.
Para la semántica que desee, tendría una bandera "activa" o "viva" en su clase "Fuego" (u Objeto si corresponde). Entonces, el mundo en ocasiones verificaría su inventario de objetos y se desharía de los que ya no están activos.
-
Una nota más es que su código, como está escrito, tiene Fire heredando de forma privada de Object, ya que es el valor predeterminado, aunque es mucho menos común que el público en espíritu. Probablemente deberías hacer explícito el tipo de herencia, y sospecho que realmente quieres la herencia pública.
Prefiero un modelo de propiedad más estricto. El mundo debería poseer los objetos y ser responsable de limpiarlos. O bien tiene mundo eliminar hacer eso o tener actualización (u otra función) devolver un valor que indica que un objeto debe ser eliminado.
También estoy adivinando que en este tipo de patrón, querrá hacer una referencia de contar sus objetos para evitar terminar con punteros colgantes.
eliminar esto es muy arriesgado No hay nada "incorrecto" con eso. Es legal Pero hay tantas cosas que pueden salir mal cuando lo usa que deberían usarse con moderación.
Considere el caso donde alguien tiene punteros abiertos o referencias al objeto, y luego se va y se elimina a sí mismo.
En la mayoría de los casos, creo que la vida del objeto debe ser administrada por el mismo objeto que la crea. Simplemente hace que sea más fácil saber dónde ocurre la destrucción.