c++ - Destructores Singleton
memory-management (11)
¿Los objetos de Singleton que no usan los contadores de instancia / referencia se consideran fugas de memoria en C ++?
Sin un contador que requiera la eliminación explícita de la instancia de singleton cuando el recuento es cero, ¿cómo se elimina el objeto? ¿Lo limpia el sistema operativo cuando finaliza la aplicación? ¿Y si ese Singleton hubiera asignado memoria en el montón?
En pocas palabras, ¿tengo que llamar al destructor de Singelton o puedo confiar en que se limpiará cuando finalice la aplicación?
¿Cómo estás creando el objeto?
Si está utilizando una variable global o una variable estática, se llamará al destructor, suponiendo que el programa sale normalmente.
Por ejemplo, el programa
#include <iostream>
class Test
{
const char *msg;
public:
Test(const char *msg)
: msg(msg)
{}
~Test()
{
std::cout << "In destructor: " << msg << std::endl;
}
};
Test globalTest("GlobalTest");
int main(int, char *argv[])
{
static Test staticTest("StaticTest");
return 0;
}
Imprime
In destructor: StaticTest
In destructor: GlobalTest
Cada idioma y entorno diferirán, aunque estoy de acuerdo con @Aaron Fisher en que un singleton tiende a existir durante la duración del proceso.
En el ejemplo de C ++, usando una expresión típica de singleton:
Singleton &get_singleton()
{
static Singleton singleton;
return singleton;
}
la instancia de Singleton se construirá la primera vez que se llame a la función, y la misma instancia tendrá su llamada al destructor durante la fase del destructor estático global al apagar el programa.
Como muy a menudo, "depende". En cualquier sistema operativo digno de este nombre, cuando finaliza su proceso, se liberará toda la memoria y otros recursos utilizados localmente dentro del proceso. Simplemente no necesita preocuparse por eso.
Sin embargo, si su singleton está asignando recursos de por vida fuera de su propio proceso (tal vez un archivo, un mutex con nombre o algo similar), entonces necesita considerar la limpieza adecuada.
RAII te ayudará aquí. Si tienes un escenario como este:
class Tempfile
{
Tempfile() {}; // creates a temporary file
virtual ~Tempfile(); // close AND DELETE the temporary file
};
Tempfile &singleton()
{
static Tempfile t;
return t;
}
... entonces puede estar seguro de que su archivo temporal SERÁ cerrado y eliminado sin embargo, su aplicación se cierra. Sin embargo, esto NO es seguro para subprocesos, y el orden de eliminación de objetos puede no ser el esperado o requerido.
sin embargo, si su singleton se implementa como THIS
Tempfile &singleton()
{
static Tempfile *t = NULL;
if (t == NULL)
t = new Tempfile();
return *t;
}
... entonces tienes una situación diferente. La memoria utilizada por el archivo temporal se recuperará, pero el archivo NO se eliminará porque no se invocará el destructor.
Cualquier clase de asignación, excepto aquellas en memorias compartidas, son limpiadas automáticamente por el sistema operativo cuando termina el proceso. Por lo tanto, no debería tener que llamar explícitamente al destructor singleton. En otras palabras, no hay fugas ...
Además, una implementación singleton típica como Singleton de Meyers no solo es segura para subprocesos durante la inicialización en la primera llamada, sino que también garantiza una finalización elegante cuando la aplicación sale (se invoca el destructor).
De todos modos, si la aplicación recibe una señal de Unix (es decir, SIGTERM o SIGHUP ), el comportamiento predeterminado es finalizar el proceso sin llamar a los destructores de objetos asignados estáticos (singletons). Para superar este problema en el caso de estas señales, es posible disponer de un controlador que llame a la salida, o disponer de una salida, tal controlador - signal(SIGTERM,exit);
Debería limpiar explícitamente todos sus objetos. Nunca confíe en el sistema operativo para hacer la limpieza por usted.
Donde usualmente uso un singleton es para encapsular el control de algo como un archivo, recurso de hardware, etc. Si no saco adecuadamente esa conexión, puedo frenar fácilmente los recursos del sistema. La próxima vez que se ejecute la aplicación, podría fallar si el recurso todavía está bloqueado por la operación anterior. Otro problema podría ser que cualquier finalización, como escribir un búfer en el disco, podría no ocurrir si todavía existe en un búfer propiedad de una instancia singleton.
Este no es un problema de pérdida de memoria; el problema es más que puede que esté filtrando recursos que no sean de memoria y que no se puedan recuperar tan fácilmente.
Depende de tu definición de fuga. El aumento de la memoria sin consolidar es una filtración en mi libro, un singleton no está desatado. Si no proporciona recuento de referencias, mantiene intencionalmente la instancia activa. No es un accidente, no es una fuga.
El destructor de su contenedor singleton debe eliminar la instancia, no es automático. Si solo asigna memoria y no tiene recursos del sistema operativo, no tiene sentido.
El SO recuperará cualquier memoria de pila asignada por su proceso y no liberada (eliminada). Si está utilizando la implementación más común de singleton, que usa variables estáticas, esto también se eliminará cuando finalice la aplicación.
* Esto no significa que deba ir por punteros nuevos y nunca limpiarlos.
En idiomas como C ++ que no tienen recolección de basura, se recomienda limpiar antes de la finalización. Puedes hacer esto con una clase de amigo destructor.
class Singleton{
...
friend class Singleton_Cleanup;
};
class Singleton_Cleanup{
public:
~Singleton_Cleanup(){
delete Singleton::ptr;
}
};
Cree la clase de limpieza al iniciar el programa y luego al salir del destructor se llamará limpiar el singleton. Esto puede ser más detallado que dejarlo ir al sistema operativo, pero sigue los principios de RAII y, dependiendo de los recursos asignados en su objeto singleton, podría ser necesario.
Es folclore liberar asignaciones de memoria global explícitamente antes de que la aplicación finalice. Supongo que la mayoría de nosotros lo hacemos por costumbre y porque creemos que es una especie de "olvidar" una estructura. En el mundo C, es una ley de simetría que cualquier asignación debe tener una desasignación en alguna parte. Los programadores de C ++ piensan diferente si conocen y practican RAII.
En los viejos tiempos de, por ejemplo, AmigaOS, había pérdidas de memoria REALES. Cuando olvidó desasignar la memoria, NUNCA volverá a estar accesible hasta que se restablezca el sistema.
No sé de ningún sistema operativo de escritorio que se respete en estos días que permita que las pérdidas de memoria se escapen del espacio de direcciones virtuales de una aplicación. Su millaje puede variar en los dispositivos integrados cuando no hay una contabilidad extensa.
Puede confiar en que el sistema operativo lo limpia.
Dicho esto, si está en un lenguaje recogido de basura con finalizadores en lugar de destructores, es posible que desee tener un procedimiento de apagado ordenado que pueda cerrar directamente sus singletons para que puedan liberar recursos críticos en caso de que se utilicen recursos del sistema que no funcionarán. limpiarse correctamente simplemente terminando la aplicación. Esto se debe a que los finalizadores se ejecutan en una especie de "mejor esfuerzo" en la mayoría de los idiomas. Por otro lado hay muy pocos recursos que necesitan este tipo de fiabilidad. manejadores de archivos, memoria, etc. todos regresan al sistema operativo limpiamente.
Si está utilizando un singleton que está asignado de forma perezosa (es decir, con un idioma de bloqueo de comprobación triple) en un lenguaje como c ++ con destructores reales en lugar de finalizadores, entonces no puede confiar en que se invoque su destructor durante el apagado del programa. Si está utilizando una única instancia estática, el destructor se ejecutará después de que el principal se complete en algún momento.
De todos modos, cuando el proceso finaliza, toda la memoria vuelve al sistema operativo.
Un singleton sería una instancia de tu objeto. Es por eso que no requiere un contador. Si va a existir para la duración de su aplicación, entonces el destructor predeterminado estará bien. La memoria será, en cualquier caso, recuperada por el sistema operativo cuando finalice el proceso.