2016-06-03 21 views
6

Mi sono appena bloccato con la seguente domanda: se questo causa un comportamento indefinito o no e perché?std :: map operator [] - comportamento non definito?

std::map<int, int> m; 
m[10] += 1; 

Compila e funziona perfettamente ma non prova nulla. Assomiglia un esempio comune UB i = ++i + i++; dal operator[] ha effetti collaterali, ma d'altra parte assumere alcun ordine di valutazione (da sinistra a destra e da destra a sinistra) mi porta lo stesso stato finale della mappa

P.S. possibilmente correlati: http://en.cppreference.com/w/cpp/language/eval_order

modificare

Scusate ragazzi ho dovuto scrivere

m[10] = m[10] + 1; 
+0

Tali domande mostrano davvero che il linguaggio è diventato molto complicato – Slava

+1

Come può essere ** UB **? – Destructor

+0

@Destructor, si prega di vedere la modifica, qualcosa cambia? – DimG

risposta

3

Non c'è nulla di definito su questo. operator[] restituisce un riferimento lvalue alla voce della mappa (che crea se necessario). Quindi stai semplicemente incrementando questa espressione lvalue, cioè la voce sottostante.

Le regole per l'ordine di valutazione stabiliscono che per un'operazione di assegnazione di modifica, l'effetto collaterale viene sequenziato rigorosamente dopo la valutazione di entrambi gli operandi di sinistra (cioè lvalue riferimento alla voce della mappa) e destra (ovvero la costante 1). Non c'è alcuna ambiguità in questo esempio.

UPDATE: Nell'esempio aggiornato non cambia nulla. Anche in questo caso l'effetto collaterale della modifica di m[10] viene sequenziato rigorosamente dopo le altre operazioni (vale a dire valutare come un lvalue a destra, valutarlo a destra ed eseguire l'aggiunta).

La regola sequenziamento rilevante, da cppreference:

8) L'effetto collaterale (modifica dell'argomento sinistra) del built-in operatore di assegnazione e di tutte incorporate operatori di assegnazione composti sequenziato dopo il calcolo del valore (ma non gli effetti collaterali) di sinistra e di destra argomenti, e viene sequenziato prima che il valore calcolo dell'espressione di assegnazione (cioè, prima di tornare il riferimento all'oggetto modificato)

+0

per favore, vedi la modifica, la risposta cambia? – DimG

+0

Probabilmente ho usato in modo fuorviante la frase "affetti laterali" nel significato generalizzato, ero specificamente preoccupato per l'effetto collaterale della struttura del contenitore: la parte del lato sinistro deve essere in qualche modo * valutata * (quel valore che vogliamo cambiare). Questa valutazione è un'operazione costosa poiché inseriamo un nuovo valore per mappare. Immagina che questa forza sottostante l'albero di ricerca venga riequilibrata drasticamente. Inoltre, come hai gentilmente risposto, la parte della parte destra deve essere * valutata *. La regola che hai citato (come gli altri, come posso vedere) non dice nulla su * ordine delle computazioni di valore *. – DimG

+0

Pertanto, in base alla pagina citata, queste modifiche alla struttura ad albero possono sovrapporsi: "le valutazioni di A e B sono annullate: possono essere eseguite in qualsiasi ordine e possono sovrapporsi (all'interno di un singolo thread di esecuzione, il compilatore può interlacciare le istruzioni della CPU che comprendono A e B) ". Il che a sua volta rende il comportamento indefinito – DimG

0

Non sono sicuro di quale sia la tua preoccupazione (e forse dovresti chiarire la tua domanda se quella risposta non è sufficiente), ma m[10] += 1; non viene tradotto in m[10] = m[10] + 1; perché m è un tipo di classe definito dall'utente e operatori sovraccaricati don ' t vengono tradotti dal compilatore, mai. Per a e b oggetti con un utente definito tipo di classe:

  • a+=b non significa a = a + b (a meno che non si rendono così)
  • a!=b non significa !(a==b) (a meno che non si rendono così)

Inoltre, le chiamate di funzione non vengono mai duplicate.

Quindi m[10] += 1; significa chiamata una volta sovraccaricata operator[]; return type è un riferimento, quindi l'espressione è un lvalue; quindi applicare l'operatore integrato += al valore l.

Non esiste un ordine di valutazione. Non ci sono nemmeno più ordini di valutazione possibili!

Inoltre, è necessario ricordare che il std::map<>::operator[] non si comporta come std::vector<>::operator[] (o std::deque 's), perché il map è un'astrazione completamente diversa: vector e deque sono implementazioni del concetto di sequenza (dove le questioni di posizione), ma map è un contenitore associativo (dove materie "chiave", non posizione):

  • std::vector<>::operator[] richiede un indice numerico, e non ha senso se tale indice non si riferisce ad un elemento del vettore.
  • std::map<>::operator[] prende una chiave (che può essere qualsiasi tipo che soddisfa i vincoli di base) e creerà una coppia (chiave, valore) se non esiste.

noti che per questo motivo, std::map<>::operator[] è intrinsecamente un'operazione modifica e quindi non const, mentre std::vector<>::operator[] non è intrinsecamente modificante ma può consentire la modifica tramite il riferimento restituito, ed è quindi "transitivamente" const: v[i] sarà un lvalue modificabile se v è un vettore non const e un valore const se v è un vettore const.

Quindi nessuna preoccupazione, il codice ha un comportamento perfettamente ben definito.

Problemi correlati