2012-05-02 25 views
10

Tengo las siguientes clases que intentan implementar un Singleton genérico.G ++ obtiene el orden de destrucción de variables estáticas incorrecto

struct BaseObject 
{ 
    virtual ~BaseObject() {} 
}; 
class _helper 
{ 
private: 
    template<typename T> friend class Singleton; 
    set<BaseObject*> _s; 
    static _helper& _get() 
    { 
     static _helper t; 
     return t; 
    } 
    _helper() 
    { 
     cout<<" _helper ctor"<<endl; 
    } 
    ~_helper() 
    { 
     cout<<" _helper dtor"<<endl; 
     //assert(_s.empty()); 
    } 
}; 

// Singleton<foo>::Instance() returns a unique instance of foo 
template <typename T> 
class Singleton : virtual private T 
{ 
public: 
    static T& Instance() 
    { 
     static Singleton<T> _T; 
     return _T; 
    } 

private: 
    Singleton() 
    { 
     cout<<"inserting into helper "<<typeid(T).name()<<" ptr "<<this<<endl; 
     assert(!_helper::_get()._s.count(this)); 
     _helper::_get()._s.insert(this); 
    } 
    ~Singleton() 
    { 
     cout<<"erasing from helper "<<typeid(T).name()<<" ptr "<<this<<endl; 
     assert(_helper::_get()._s.count(this)); 
     _helper::_get()._s.erase(this); 
    } 
}; 

Ahora si llamo Singleton< bar>::Instance() seguido por Singleton< foo>::Instance(), debería ver la siguiente salida:

inserting into helper 3bar ptr 0x509630 
_helper ctor 
inserting into helper 3foo ptr 0x509588 
erasing from helper 3foo ptr 0x509588 
erasing from helper 3bar ptr 0x509630 
_helper dtor 

Sin embargo, en algunos casos, veo lo siguiente:

inserting into helper 3bar ptr 0x509630 
_helper ctor 
inserting into helper 3foo ptr 0x509588 
erasing from helper 3bar ptr 0x509630 
_helper dtor 
erasing from helper 3foo ptr 0x509588 

Nótese que en el segundo caso, bar y foo se destruyó en el mismo orden en que se construyeron. Esto parece ocurrir cuando los foo y bar únicos se crean instancias dentro de una biblioteca compartida (.so) como referencias estáticas:

static bar& b = Singleton<bar>::Instance(); 
static foo& f = Singleton<foo>::Instance(); 

Cualquier idea por qué haría eso?

+0

No creo que C++ haga ninguna garantía sobre el orden en que se llamarán los destructores de variables estáticas? –

+2

¿Están en la misma unidad de traducción? – GManNickG

+1

Tengo que poner algunos dólares de opinión aquí. Esta es una aplicación incorrecta del patrón singleton. El patrón singleton debe reservarse estrictamente para aquellos objetos que DEBEN ser solitarios dentro de un programa, no aplicados a algún objeto que simplemente exista solo una vez en el programa.Si desea un recurso de patrón singleton genérico, debe heredar FROM, lo que hace que el objeto no pueda existir en varias instancias. Si necesita un global, use uno. Singleton! = Variable global. –

Respuesta

1

Esto puede suceder si los singleton y el helper están ubicados en diferentes unidades de traducción u otros objetos compartidos. Tenga en cuenta que es bastante difícil predecir en qué unidad de traducción terminará una instancia de plantilla. Recuerde también que cada objeto compartido puede obtener su propia instancia de decir Singleton<foo>::_T. Entonces usted tiene algún tipo de singleton por objeto compartido (no muy útil en mi humilde opinión).

Tenga en cuenta que su ayudante se destruye antes de el último objeto se elimina de ella. Esto dará lugar a un bloqueo del programa al salir. Sí, esto mismo me ha pasado a mí. Tendrá que implementar un contador de objetos en la clase _helper para que no se destruya hasta que haya al menos un objeto registrado con él. Alternativamente, asigne todos los singletons en el montón y deje que el helper los destruya cuando termine su vida útil.

update Esta revocación de la orden de destrucción probablemente no pueda suceder si los dos objetos estáticos son propiedad de la misma biblioteca dinámica. Definitivamente puede y de hecho sucede lo contrario. Here a los programadores se les aconseja no exportar objetos estáticos a través de los límites dinámicos de la biblioteca.

+2

No debería suceder incluso en ese caso. El orden de construcción a través de los límites de TU no está definido, pero el estándar exige que el orden de destrucción sea el orden inverso de finalización de los constructores apropiados (o inicialización dinámica). Es decir, dados dos objetos estáticos 'a' y' b' en dos unidades de traducción, el estándar requiere una de dos órdenes posibles: 'a, b, ~ b, ~ a' o' b, a, ~ a, ~ b ' –

+0

Exactamente lo que pensé que debería ser el caso. –

+2

El estándar no dice nada acerca de las bibliotecas cargadas dinámicamente. En la práctica, cuando se descarga una biblioteca de este tipo, se destruyen todos los objetos estáticos que "pertenecen", independientemente de cómo se construyan los objetos estáticos en otros .so/.dlls. –

0

static _helper t; define una dirección hasta donde yo sé.

static _helper& _get() 
{ 
    static _helper t; 
    return t; 
} 

Parece que estás intentando usarlo para 2 objetos distintos.

De todos modos, nunca he visto una plantilla que se utiliza para un singleton. Y en tu caso, parece que estás tratando de destruir un singleton. Eso también no recuerdo haberlo visto antes. Un singleton generalmente se crea una vez y permanece hasta que abandone el programa (y todavía se asigna cuando se va).

De lo contrario, es posible que desee buscar punteros compartidos o objetos contados intrusivos?