c++ memory static singleton dynamic-memory-allocation

Asignación de memoria dinámica/estática vs. Heap para instancia de clase singleton C++



memory static (2)

La principal diferencia es que al usar una static local, el objeto se destruirá al cerrar el programa, en lugar de eso, los objetos asignados a un montón simplemente se abandonarán sin ser destruidos.

Tenga en cuenta que en C ++, si declara una variable estática dentro de una función, se inicializará la primera vez que ingrese el alcance, no al inicio del programa (como sucede en el caso de las variables de duración estática global).

En general, a lo largo de los años, cambié de la inicialización perezosa a la inicialización controlada explícita porque el inicio y el cierre del programa son fases delicadas y bastante difíciles de depurar. Si su clase no está haciendo nada complejo y simplemente no puede fallar (por ejemplo, es solo un registro), incluso la inicialización perezosa está bien ... de lo contrario, tener el control le ahorrará muchos problemas.

Un programa que falla antes de ingresar la primera instrucción de main o después de ejecutar la última instrucción de main es más difícil de depurar.

Otro problema del uso de la construcción perezosa de singletons es que si su código es multihilo, debe prestar atención al riesgo de tener subprocesos simultáneos que inicien el singleton al mismo tiempo. Hacer la inicialización y el cierre en un solo contexto de subproceso es más simple.

Mi pregunta específica es que cuando se implementa una clase singleton en C ++, ¿hay diferencias sustanciales entre los dos códigos a continuación con respecto al rendimiento, los problemas secundarios o algo así?

class singleton { // ... static singleton& getInstance() { // allocating on heap static singleton* pInstance = new singleton(); return *pInstance; } // ... };

y esto:

class singleton { // ... static singleton& getInstance() { // using static variable static singleton instance; return instance; } // ... };


(Tenga en cuenta que la falta de referencia en la implementación basada en el montón no debería afectar al rendimiento, ya que AFAIK no se genera ningún código de máquina adicional para la eliminación de la referencia. Es solo una cuestión de sintaxis distinguir de los punteros).

ACTUALIZAR:

Tengo respuestas y comentarios interesantes que trato de resumir aquí. (Se recomienda leer las respuestas detalladas para los interesados):

  • En el singleton que usa una variable local estática , el destructor de la clase se invoca automáticamente en la terminación del proceso, mientras que en el caso de asignación dinámica , tiene que administrar la destrucción de objetos de alguna manera, por ejemplo, utilizando punteros inteligentes:

static singleton& getInstance() { static std::auto_ptr<singleton> instance (new singleton()); return *instance.get(); }

  • El singleton que usa la asignación dinámica es "más perezoso" que la variable singleton estática, como en el caso posterior, la memoria requerida para el objeto singleton se reserva (siempre?) Al inicio del proceso (como parte de toda la memoria requerida para cargar el programa ) y solo se aplaza la llamada del constructor de singleton a getInstance() tiempo de llamada. Esto puede importar cuando sizeof(singleton) es grande.

  • Ambos son seguros para subprocesos en C ++ 11. Pero con versiones anteriores de C ++, es específico de la implementación.

  • El caso de asignación dinámica utiliza un nivel de direccionamiento indirecto para acceder al objeto singleton, mientras que en el caso del objeto singleton estático, la dirección directa del objeto está determinada y codificada en el momento de la compilación.


PD: He corregido la terminología que usé en la publicación original de acuerdo con la respuesta de @TonyD.


  • la new versión obviamente necesita asignar memoria en tiempo de ejecución, mientras que la versión sin puntero tiene la memoria asignada en tiempo de compilación (pero ambas necesitan hacer la misma construcción)

  • La new versión no invocará el destructor del objeto al finalizar el programa, pero la versión no new : se podría usar un puntero inteligente para corregir esto.

    • debe tener cuidado de que los destructores de algunos objetos estáticos / de espacio de nombres no invocan su singleton después de que se haya ejecutado el destructor de su instancia local estática ... si le preocupa esto, tal vez debería leer un poco más sobre las vidas de Singleton y enfoques para su gestión. El diseño moderno de C ++ de Andrei Alexandrescu tiene un tratamiento muy legible.
  • bajo C ++ 03, está definido por la implementación si será seguro para subprocesos. (Creo que GCC tiende a serlo, mientras que Visual Studio no tiende a hacer comentarios para confirmar / corregir).

  • bajo C ++ 11, es seguro: 6.7.4 "Si el control ingresa la declaración simultáneamente mientras la variable se está inicializando, la ejecución concurrente esperará a que se complete la inicialización". (sans recursion).

Discusión sobre la asignación e inicialización de tiempo de compilación versus tiempo de ejecución

Por la forma en que ha redactado su resumen y algunos comentarios, sospecho que no está comprendiendo completamente un aspecto sutil de la asignación e inicialización de variables estáticas ...

Supongamos que su programa tiene 3 int s de 32 bits estáticos locales - a , c - en diferentes funciones: es probable que el compilador compile un binario que le indique al cargador del sistema operativo que deje 3x32 bits = 12 bytes de memoria para esas estadísticas. El compilador decide en qué compensaciones está cada una de esas variables: puede poner a hex de 1000 en el segmento de datos, b en 1004 y c en 1008. Cuando el programa se ejecuta, el cargador del sistema operativo no necesita asignar memoria para cada uno por separado: lo único que sabe es el total de 12 bytes, que pueden o no haber sido solicitados específicamente para la inicialización de 0, pero puede querer hacerlo de todos modos para garantizar que el proceso no pueda ver el contenido de memoria restante de otros Programas de los usuarios. Las instrucciones de código de máquina en el programa normalmente codificarán las compensaciones 1000, 1004, 1008 para los accesos a, b y c , por lo que no es necesaria la asignación de esas direcciones en tiempo de ejecución.

La asignación de memoria dinámica es diferente porque los punteros (por ejemplo, p_a , p_b , p_c ) recibirán direcciones en el momento de la compilación como se acaba de describir, pero adicionalmente:

  • la memoria apuntada a (cada uno de a , c ) se debe encontrar en tiempo de ejecución (normalmente cuando la función estática se ejecuta por primera vez, pero el compilador puede hacerlo antes según mi comentario en la otra respuesta), y
    • si el sistema operativo le da al sistema demasiada memoria actualmente para que la asignación dinámica tenga éxito, entonces la biblioteca de programas solicitará al sistema operativo más memoria (por ejemplo, utilizando sbreak() ), que el sistema operativo normalmente eliminará por razones de seguridad
    • Las direcciones dinámicas asignadas para cada uno de a , b deben copiarse de nuevo en los punteros p_a , p_b y p_c .

Este enfoque dinámico es claramente más complicado.