L’istanza Singleton dichiarata come variabile statica del metodo GetInstance, è thread-safe?

Ho visto implementazioni di pattern Singleton in cui la variabile di istanza è stata dichiarata come variabile statica nel metodo GetInstance. Come questo:

SomeBaseClass &SomeClass::GetInstance() { static SomeClass instance; return instance; } 

Vedo i seguenti lati positivi di questo approccio:

  • Il codice è più semplice, perché è il compilatore che è responsabile della creazione di questo object solo quando GetInstance ha chiamato per la prima volta.
  • Il codice è più sicuro, perché non c’è altro modo per ottenere riferimenti all’istanza, ma con il metodo GetInstance e non c’è altro modo per cambiare l’istanza, ma nel metodo GetInstance.

Quali sono i lati negativi di questo approccio (eccetto che questo non è molto OOP-ish)? Questo thread-safe?

In C ++ 11 è thread-safe:

§6.7 [stmt.dcl] p4 Se il controllo immette la dichiarazione contemporaneamente durante l’inizializzazione della variabile, l’esecuzione simultanea deve attendere il completamento dell’inizializzazione.

In C ++ 03:

  • Sotto g ++ è thread-safe.
    Ma questo perché g ++ aggiunge esplicitamente il codice per garantirlo.

Un problema è che se hai due singleton e ci provano e si usano l’un l’altro durante la costruzione e la distruzione.

Leggi questo: Ricerca di problemi di ordine di inizializzazione statici C ++

Una variazione su questo problema è se si accede al singleton dal distruttore di una variabile globale. In questa situazione il singleton è stato definitivamente distrutto, ma il metodo get restituirà comunque un riferimento all’object distrutto.

Ci sono modi per aggirare questo, ma sono disordinati e non ne vale la pena. Basta non accedere a un singleton dal distruttore di una variabile globale.

Una definizione più sicura ma brutta:
Sono sicuro che è ansible aggiungere alcuni macro appropriati per riordinare ciò

 SomeBaseClass &SomeClass::GetInstance() { #ifdef _WIN32 Start Critical Section Here #elif defined(__GNUC__) && (__GNUC__ > 3) // You are OK #else #error Add Critical Section for your platform #endif static SomeClass instance; #ifdef _WIN32 END Critical Section Here #endif return instance; } 

Non è thread-safe come mostrato. Il linguaggio C ++ è silenzioso sui thread in modo da non avere garanzie intrinseche dalla lingua. Dovrai utilizzare le primitive di sincronizzazione della piattaforma, ad esempio Win32 :: EnterCriticalSection (), per proteggere l’accesso.

Il tuo approccio particolare sarebbe problematico b / c il compilatore inserirà un codice (non thread safe) per inizializzare l’ instance statica al primo richiamo, molto probabilmente lo sarà prima che il corpo della funzione inizi l’esecuzione (e quindi prima che qualsiasi sincronizzazione possa essere invocata .)

L’utilizzo di un puntatore membro globale / statico su SomeClass e l’inizializzazione all’interno di un blocco sincronizzato si dimostrerebbe meno problematico da implementare.

 #include  namespace { //Could be implemented as private member of SomeClass instead.. boost::shared_ptr g_instance; } SomeBaseClass &SomeClass::GetInstance() { //Synchronize me eg ::EnterCriticalSection() if(g_instance == NULL) g_instance = boost::shared_ptr(new SomeClass()); //Unsynchronize me eg :::LeaveCriticalSection(); return *g_instance; } 

Non l’ho compilato quindi è solo a scopo illustrativo. Si basa anche sulla libreria boost per ottenere la stessa durata (o circa) del tuo esempio originale. Puoi anche usare std :: tr1 (C ++ 0x).

Secondo le specifiche questo dovrebbe funzionare anche in VC ++. Qualcuno sa se lo fa?

Basta aggiungere parole chiave volatili. Il compilatore visivo c ++ dovrebbe quindi generare mutex se il doc su msdn è corretto.

 SomeBaseClass &SomeClass::GetInstance() { static volatile SomeClass instance; return instance; } 

Condivide tutti i difetti comuni delle implementazioni Singleton, vale a dire:

  • È imansible da testare
  • Non è thread-safe (questo è abbastanza banale da vedere se si immaginano due thread che entrano nella funzione allo stesso tempo)
  • È una perdita di memoria

Raccomando di non usare mai Singleton in alcun codice di produzione.