2015-11-08 12 views
13
map<int, int> mp; 
printf("%d ", mp.size()); 
mp[10]=mp.size(); 
printf("%d\n", mp[10]); 

Questo codice produce una risposta che non è molto intuitivo:Ordine di valutazione di istruzione di assegnazione in C++

capisco perché succede - il lato sinistro della attributo restituisce il riferimento al valore sottostante di mp[10] e allo stesso tempo crea il valore summenzionato, e solo allora viene valutata la parte destra, utilizzando il nuovo size() calcolato della mappa.

Questo comportamento è indicato ovunque nello standard C++? O l'ordine di valutazione è indefinito?

Il risultato è stato ottenuto utilizzando g ++ 5.2.1.

+0

Questo è davvero curioso. gcc 4.8.4 restituisce anche 0 1, ma clang 3.4 restituisce 0 0. – Harald

+3

Si dovrebbe fare attenzione con la parola "undefined". Non è sinonimo di "non specificato". – molbdnilo

+0

E per aggiungere alla dichiarazione di Redcrash, gcc 4.9.2 e clang 3.8 di venerdì sera seguono entrambi i risultati 4.8.4 e 3.4. Rafforzando ulteriormente che non vi è alcun obbligo da qualche parte per avere un ordine specifico per questo. –

risposta

12

Sì, questo è coperto dallo standard ed è il comportamento specificato. Questo caso particolare è coperto da una recente proposta di standard C++: N4228: Refining Expression Evaluation Order for Idiomatic C++ che cerca di perfezionare l'ordine delle regole di valutazione per renderlo ben specificato in determinati casi.

Esso descrive il problema come segue:

ordine di valutazione di espressione è un argomento di discussione ricorrente nella comunità di C++ . In breve, data un'espressione come f (a, b, c), l'ordine in cui vengono valutate le sottoespressioni f, a, b, c non viene specificato dallo standard. Se due di queste sotto-espressioni capita di modificare lo stesso oggetto senza punti di sequenza intermedi, il comportamento del programma non è definito. Ad esempio, l'espressione f (i ++, i) dove i è una variabile porta a comportamento non definito, così come v [i] = i ++. Anche quando il comportamento non è indefinito, il risultato della valutazione di un'espressione può ancora essere indovinato da chiunque. Si consideri il seguente frammento di programma:

#include <map> 

int main() { 
    std::map<int, int> m; 
    m[0] = m.size(); // #1 
} 

Quale dovrebbe essere la carta oggetto m simile dopo la valutazione della dichiarazione segnato 1 #? {{0, 0}} o {{0, 1}}?

Noi sappiamo che se non specificato le valutazioni dei sub-espressioni sono non in sequenza, questo è dal draft C++11 standard sezione 1.9 esecuzione programma che dice:

Salvo quando diversamente indicato, valutazioni di operandi dei singoli operatori e di sottoespressioni delle singole espressioni sono non in sequenza. [...]

e tutta la sezione 0.123.138,087856 millionsassegnazione e assegnazione composto operatori [expr.ass] dice è:

[...] In tutti i casi, l'assegnazione viene sequenziato dopo che il valore calcolo degli operandi destra e sinistra, e prima che il calcolo del valore dell'espressione di assegnazione. [...]

Quindi questa sezione non inchiodare giù l'ordine di valutazione, ma sappiamo che questo non è un comportamento indefinito in quanto sia operator [] e size() sono chiamate di funzione e la sezione 1.9 ci dice (emphasis mine):

[...] Quando si chiama una funzione (se la funzione è in linea), ogni valore calcolo e lato effetto associato con qualsiasi espressione argomentazione, o con l'espressione suffisso designa la funzione chiamata, è sequenziata prima dell'esecuzione di ogni espressione o affermazione nel corpo della funzione chiamata.[Nota: i calcoli del valore e gli effetti collaterali associati alle diverse espressioni degli argomenti non sono seguiti. -end note] Ogni valutazione nella funzione chiamante (comprese altre chiamate di funzione) che non sia specificatamente sequenziata prima o dopo l'esecuzione del corpo della funzione chiamata viene indeterminatamente sequenziata con rispetto all'esecuzione della funzione chiamata 0,9 [...]

nota, copro il secondo esempio interessante dal N4228 proposta in tal question here.

Aggiornamento

sembra una versione rivista del N4228 era accepted by the Evolution Working Group at the last WG21 meeting ma la carta (P0145R0) non è ancora disponibile. Quindi questo potrebbe non essere più specificato in C++ 17.

+0

Nota, per mostrare che questo è non specificato rispetto al comportamento non definito, dobbiamo citare la sezione '1.9', la citazione della sezione' 5.17' non è sufficiente. –

+0

Nota, come da [proposta p0145r3] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0145r3.pdf) questo non è più un comportamento non specificato. § [espr.ass] ora dice "L'operando di destra è sequenziato prima dell'operando di sinistra" –

3

sono sicuro che la norma non specifica per un'espressione x = y; quale ordine x o y viene valutata nello standard C++ (questo è il motivo per cui non si può fare *p++ = *p++ per esempio, a causa p++ non si fa in un ordine definito).

In altre parole, per garantire l'ordine x = y; in un ordine definito, è necessario suddividerlo in due punti di sequenza.

T tmp = y; 
x = tmp; 

(Naturalmente, in questo caso particolare, si può presumere che il compilatore preferisce fare operator[] prima size() perché può quindi memorizzare il valore direttamente nel risultato di operator[] invece di tenerla in un luogo temporaneo, per conservarlo più tardi, dopo operator[] è stata valutata - ma sono abbastanza sicuro che il compilatore non ha bisogno di farlo in questo ordine)

+1

La tua pausa suggerita introduce oggetti extra ed effetti collaterali - usando un riferimento di qualche tipo potrebbe essere meglio –

+0

Solo se 'y' non è un tipo banale come' int' o 'size_t', ovviamente. –

7

Dalla serie C++ 11 (sottolineatura mia):

5.17 Operatori di assegnazione e assegnazione assegnazione

1 L'operatore di assegnazione (=) e gli operatori di assegnazione composto si raggruppano tutti da destra a sinistra. Tutti richiedono un lvalue modificabile come il loro operando di sinistra e restituiscono un lvalue che si riferisce all'operando di sinistra. Il risultato in tutti i casi è un campo di bit se l'operando di sinistra è un campo di bit. In tutti i casi, l'assegnazione viene eseguita in sequenza dopo il calcolo del valore degli operandi di destra e di sinistra e prima del calcolo del valore dell'espressione di assegnazione.

Se l'operando di sinistra viene valutato per primo o se l'operando di destra viene valutato per primo, non viene specificato dalla lingua. Un compilatore è libero di scegliere prima di valutare l'operando. Poiché il risultato finale del codice dipende dall'ordine di valutazione degli operandi, direi che si tratta di un comportamento non specificato piuttosto che di un comportamento non definito.

1.3.25 comportamento non specificato

comportamento per un costrutto programma ben formato e dati corretti, che dipende dall'implementazione

1

Diamo un'occhiata a ciò che il codice si rompe a:

mp.operator[](10).operator=(mp.size()); 

che racconta più o meno la storia che nella prima parte si crea una voce a 10 e nella seconda parte la dimensione del contenitore è assegnato al numero intero di riferimento in posizione di 10.

Ma ora si entra nell'ordine del problema di valutazione che non è specificato. Ecco un più semplice example.

Quando si deve chiamare map::size(), prima o dopo map::operator(int const &);?

Nessuno lo sa davvero.

Problemi correlati