2013-07-01 15 views
14

1) std :: call_oncequal è la differenza tra std :: call_once ea livello di funzione di inizializzazione statica

A a; 
std::once_flag once; 

void f () { 
    call_once (once, [ ] { a = A {....}; }); 
} 

2) funzione di livello statico

A a; 

void f () { 
    static bool b = ([ ] { a = A {....}; } (), true); 
} 
+0

I tuoi due esempi non sono equivalenti, 'call_once' ignora il valore di ritorno dell'oggetto richiamabile che lo hai passato. Probabilmente vuoi assegnare a 'a' in entrambe le espressioni lambda. –

+0

sì, ho dimenticato di cambiare prima. corretto – pal

risposta

10

Per il vostro esempio di utilizzo, la risposta di hmjd spiega pienamente che non v'è alcuna differenza (tranne che per l'ulteriore once_flag oggetto globale necessaria nel caso call_once.) Tuttavia, il caso call_once è più flessibile, dal momento che l'oggetto once_flag non è legato ad un unico scopo. Ad esempio, potrebbe essere un membro della classe e essere usato da più di una funzione:

class X { 
    std::once_flag once; 

    void doSomething() { 
    std::call_once(once, []{ /* init ...*/ }); 
    // ... 
    } 

    void doSomethingElse() { 
    std::call_once(once, []{ /*alternative init ...*/ }); 
    // ... 
    } 
}; 

Ora seconda della funzione membro è chiamato primo codice di inizializzazione può essere differente (ma l'oggetto sarà ancora inizializzata solo una volta.)

Quindi per casi semplici una statica locale funziona bene (se supportata dal compilatore) ma ci sono alcuni usi meno comuni che potrebbero essere più facili da implementare con call_once.

6

Entrambi i frammenti di codice hanno lo stesso comportamento, anche in presenza di eccezioni generate durante l'inizializzazione.

Questa conclusione si basa sulla (mia interpretazione) i seguenti citazioni del C++ 11 standard (progetto n3337):

  • 1 Sezione 6.7 Dichiarazione dichiarazione clausola 4 stati:

L'inizializzazione zero (8.5) di tutte le variabili di ambito di blocco con durata di archiviazione statica (3.7.1) o durata di memorizzazione del thread (3.7.2) viene eseguita prima di qualsiasi altra inizializzazione. L'inizializzazione costante (3.6.2) di un'entità a ambito di blocco con durata di memorizzazione statica, se applicabile, viene eseguita prima che il suo blocco venga immesso per la prima volta. Un'implementazione è autorizzata a eseguire l'inizializzazione anticipata di altre variabili di ambito di blocco con durata di archiviazione statica o di thread nelle stesse condizioni in cui è consentita un'implementazione statica di una variabile con durata di archiviazione statica o di thread nello scope namespace (3.6.2). Altrimenti, tale variabile viene inizializzata il primo controllo di tempo attraverso la sua dichiarazione; tale variabile è considerata inizializzata al completamento della sua inizializzazione. Se l'inizializzazione termina lanciando un'eccezione, l'inizializzazione non è completa, quindi verrà tentata di nuovo la volta successiva che il controllo entra nella dichiarazione. Se il controllo immette la dichiarazione contemporaneamente durante l'inizializzazione della variabile, l'esecuzione simultanea deve attendere il completamento dell'inizializzazione.88 Se il controllo rientra la dichiarazione in modo ricorsivo mentre la variabile viene inizializzata, il comportamento non è definito.

Ciò significa che in:

void f () { 
    static bool b = ([ ] { a = A {....}; } (), true); 
} 

b è garantito per essere inizializzato una sola volta, il che significa lambda viene eseguito (con successo) una sola volta, il che significa a = A {...}; viene eseguito (con successo) una sola volta.

  • 2 Sezione 30.4.4.2 Funzione chiamata una volta stati:

Un'esecuzione di call_once che non chiama la sua func è un'esecuzione passiva. Un'esecuzione di call_once che chiama la sua funzione è un'esecuzione attiva. Un'esecuzione attiva deve chiamare INVOKE (DECAY_COPY (std :: forward (func)), DECAY_COPY (std :: forward (args)) ...). Se tale chiamata a func genera un'eccezione, l'esecuzione è eccezionale, altrimenti restituisce. Un'eccezionale esecuzione deve propagare l'eccezione al chiamante di call_once. Tra tutte le esecuzioni di call_once per ogni dato once_flag: al massimo una sarà un'esecuzione di ritorno; se c'è un'esecuzione di ritorno, deve essere l'ultima esecuzione attiva; e ci sono esecuzioni passive solo se c'è un'esecuzione di ritorno.

Ciò significa che in:

void f () { 
    call_once (once, [ ] { a = A {....}; }); 

viene eseguito l'argomento lambda a std::call_once (con successo) una sola volta, il che significa a = A {...}; viene eseguito (con successo) una sola volta.

In entrambi i casi, a = A{...}; viene eseguito (con successo) una sola volta.

+2

Può essere inutilmente pedante, ma penso che il linguaggio standard (almeno le parti citate - potrebbero esserci altre sezioni che lo chiariscono di più) non implica necessariamente che il lambda verrà eseguito una sola volta, ma solo che il calcolo il risultato verrà assegnato una sola volta alla variabile. La granularità di ciò che viene protetto dall'esecuzione parallela può essere non specificata o definita dall'implementazione. – twalberg

Problemi correlati