2012-01-19 9 views
15

Is il mutex utilizzato nel metodo GetValues() rilasciato prima o dopo copia costruire l'istanza dummy?ciò che viene prima - Pila svolgimento o la copia di ritorno Valori

class Protect 
{}; 

class Test 
{ 
public: 
    Protect GetValues() const; 

private: 
    Protect m_protVal; 
    Mutex m_mutex; 
}; 

Protect Test::GetValues() const 
{ 
    CLockGuard lock(m_mutex); 

    return m_protVal; 
} 

int main(int argc, char** argv) 
{ 
    Test myTestInstance; 

    Protect dummy = myTestInstance.GetValues(); 
} 

Supponiamo CLockGuard e Mutex sono classi standard forniti con boost lib.

+4

http://stackoverflow.com/questions/3856729/how-to -use-lock-guard-when-return-protected-data – NPE

+0

Puoi eseguire il tuo programma di test passo dopo passo in un debugger (come 'gdb' su Linux) per scoprirlo. –

+2

@BasileStarynkevitch Che non gli dirà nulla di ciò che è garantito dalla lingua. –

risposta

11

Sì :-). Formalmente, esistono due “ copie ” quando si restituisce un valore : uno in un posto speciale utilizzato per restituire effettivamente il valore e il secondo dopo il ritorno, in qualsiasi punto il valore deve essere posizionato a . Entrambi o entrambi possono essere ottimizzati, tuttavia. La distruzione delle variabili locali si verifica dopo la prima, ma prima della seconda. (NRVO e RVO possono portare alla prima fase di ottimizzazione, ma non influisce il codice, dal momento che non stai tornando una variabile locale.)

+0

Se ho ottenuto la risposta giusta, la sequenza va così: 'copia m_protVal in temp',' unlock', 'copia temp in dummy'. Dove 'temp' si trova nel tuo" posto speciale ". Corretta? – nabulke

+0

Sì. Con la clausola aggiuntiva che questo 'temp' può essere" fuso "con una variabile locale o temporanea, con' dummy', o con entrambi, e la copia corrispondente eliminata. Poiché non stai restituendo una variabile locale o una temporanea, questa prima ottimizzazione non si applica al tuo codice. (Potrebbe essere un problema se si costruisce una variabile locale che viene restituita prima di acquisire il blocco.) –

+0

Suppongo che questo significhi che le copie non possono essere eliminate nei casi in cui il distruttore potrebbe lanciare, o potrebbe modificare la destinazione finale tramite un alias . Nel caso dell'eccezione, l'implementazione non deve modificare la destinazione finale prima di chiamare il distruttore.Nel caso della modifica tramite alias, l'implementazione non deve chiamare il distruttore dopo aver scritto il valore di ritorno, per paura che venga sovrascritto. O mi sbaglio, e le regole di elision copia permettono quelle trasformazioni di codice in modo che la copia alla destinazione finale non avvenga "logicamente" dopo i distruttori quando viene usato NRVO? –

2

Come di Visual C++, usando MSVC 9.0, il seguente codice

int add(int x, int y) 
{ 
    int z; 
    z = x + y; 
    return z; 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    add(10, 20); 
    return 0; 
} 

risultati in assemblaggio

int add(int x, int y) 
{ 
013613A0 push  ebp //save the frame pointer 
013613A1 mov   ebp,esp 
013613A3 sub   esp,0CCh 
013613A9 push  ebx 
013613AA push  esi 
013613AB push  edi 
013613AC lea   edi,[ebp-0CCh] 
013613B2 mov   ecx,33h 
013613B7 mov   eax,0CCCCCCCCh 
013613BC rep stos dword ptr es:[edi] 
    int z; 
    z = x + y; 
013613BE mov   eax,dword ptr [x] //load x 
013613C1 add   eax,dword ptr [y] //add y to x 
013613C4 mov   dword ptr [z],eax //store the result to z 
    return z; 
013613C7 mov   eax,dword ptr [z] //store the return value in eax 
} 
013613CA pop   edi //unwind the stack 
013613CB pop   esi 
013613CC pop   ebx 
013613CD mov   esp,ebp 
013613CF pop   ebp 
013613D0 ret 

int _tmain(int argc, _TCHAR* argv[]) 
{  
013613E0 push  ebp 
013613E1 mov   ebp,esp 
013613E3 sub   esp,0C0h 
013613E9 push  ebx 
013613EA push  esi 
013613EB push  edi 
013613EC lea   edi,[ebp-0C0h] 
013613F2 mov   ecx,30h 
013613F7 mov   eax,0CCCCCCCCh 
013613FC rep stos dword ptr es:[edi] 
    add(10, 20); 
013613FE push  14h 
01361400 push  0Ah 
01361402 call  add (136109Bh) 
01361407 add   esp,8 
    return 0; 
0136140A xor   eax,eax //store 0 to eax, the return value holder 
} 
0136140C pop   edi //unwind the stack 
0136140D pop   esi 
0136140E pop   ebx 

Questo fa dire che il valore di ritorno viene memorizzato e poi lo svolgimento pila accade!

1

Ecco un piccolo programma di test completo (oltre a James Kanzes buona spiegazione), che mostrerà se lo sblocco viene effettuato prima o dopo pila svolgimento:

#include <iostream> 

class PseudoLockGuard 
{ 
public: 
    enum LockState { IsStillLocked, IsUnlocked}; 

    PseudoLockGuard(LockState& value) : m_value(value) {}; 
    ~PseudoLockGuard() { m_value = IsUnlocked; }; 

private: 
    LockState& m_value; 
}; 

PseudoLockGuard::LockState Test() 
{ 
    PseudoLockGuard::LockState indicator = PseudoLockGuard::IsStillLocked; 

    PseudoLockGuard lock(indicator); 

    return indicator;// Will return IsStillLocked or IsUnlocked? 
} 

int main(int , char**) 
{ 
    PseudoLockGuard::LockState result = Test(); 

    std::cout << (result == PseudoLockGuard::IsStillLocked 
       ? "Return Value before Unlock" 
       : "Return Value after Unlock"); 

    // Outputs "Return Value before Unlock" 

    return 0; 
} 
2

lo standard non è Per quanto posso dire, sono particolarmente esplicito in proposito:

Gli oggetti di durata di archiviazione automatica vengono distrutti come da 6.7 quando il blocco sono sono dichiarati in uscita. - 3.7.2

All'uscita da un ambito, distruttori (12.4) sono chiamati per tutta la durata automatica stoccaggio (3.7.2) (dal nome oggetti e provvisori) che sono dichiarate in tale ambito, in ordine inverso di la loro dichiarazione - 6.6

Un'istruzione di reso con un'espressione di tipo non vuoto può essere utilizzata solo nelle funzioni restituendo un valore; il valore dell'espressione viene restituito al chiamante della funzione . L'espressione viene convertita implicitamente nel tipo restituito della funzione in cui appare. Una dichiarazione di reso può comportare la costruzione e la copia di un oggetto temporaneo (12.2). - 6.6.3

Anche quando la creazione dell'oggetto temporaneo viene evitato (12,6), tutte le semantiche restrizioni devono essere rispettati come se l'oggetto temporaneo è stato creato. - 12.2

Ciò sembra confermare in generale quello che ha detto James: su return m_protVal;, un temporaneo viene creato, e quindi distruttori di tutti gli oggetti che devono essere distrutte sono chiamati in ordine inverso della loro dichiarazione (in questo caso, solo il distruttore di lock viene chiamato). Tuttavia, non sono del tutto sicuro di come interpretare la seguente:

oggetti temporanei vengono distrutti come ultimo passo nella valutazione del full-espressione (1.9) che (lessicalmente) contiene il punto in cui sono stati creati. - 12,2

A full-espressione è definito come "come espressione che non è una sottoespressione di un'altra espressione". Non so quale parte del return m_protVal è l'espressione piena: Geordi dice che questo è un

decl'ion-ss → → decl'ion func-def → func-corpo → compound-stmt → stmt- ss → stmt → jump-stmt

mentre i di per sé è un

decl'ion-ss → → decl'ion func-def → func-corpo → compound-stmt → stmt-ss → stmt → jump-stmt → ... → id-expr → non valido-id → ident

Ciò rende chiaro se il temporaneo può essere copiato e distrutti prima del resto dei distruttori essere chiamato: Direi che non possono, come return m_protVal; provoca la fine del blocco da raggiungere, ma posso Non trovo nulla nello standard che possa confermare questo.

(D'altra parte, non vedo alcun caso in cui tale comportamento causerebbe qualsiasi rottura, nessuno dovrebbe avere un puntatore al temporaneo in modo da essere distrutto prima non è un problema, e se il temporaneo aveva un puntatore a una variabile locale, i problemi arrivano quando il temporaneo viene distrutto in seguito - non che scrivere un codice del genere sia comunque una buona idea.)

Problemi correlati