2013-08-04 16 views
9

To jest typowy wzorzec, którego używam do indeksowania tokenów w momencie ich pojawiania się: sprawdź, czy token znajduje się na mapie, a jeśli nie, dodaj go do mapy, przypisując rozmiar mapy.Dlaczego ustawienie elementu mapy na jego rozmiar zwiększa rozmiar * przed * przypisaniem go?

Kiedy robi to w C++, to nieoczekiwanie zwiększa mapie za wielkość przed przydział jest wykonany:

#include <cstdio> 
#include <map> 
using namespace std; 

int main() { 
    map<char, int> m; 
    printf("Size before adding: %d\n", m.size()); 
    m['A'] = m.size(); 
    printf("Size after adding: %d\n", m.size()); 
    printf("What was added: %d\n", m['A']); 

    return 0; 
} 

ten wypisuje:

Size before adding: 0 
Size after adding: 1 
What was added: 1 

Tak jak ja to rozumiem, to powinien oceń prawą stronę, która wynosi zero, a następnie przekaż ją do funkcji, która umieszcza "A" i zero na mapie. Ale wydaje się, że jest to ocena po rozpoczęciu przypisywania, co nie ma sensu.

Czy nie należy oceniać prawej strony przed operacją przypisania?

+0

Nie, jestem prawie pewien, że to UB. – Borgleader

+1

@Borgleader: Jest to nieokreślone zachowanie. – Nawaz

+0

@Nawaz o prawo, nieokreślony – Borgleader

Odpowiedz

6

Zachowanie jest nieokreślone, pedantycznie rzecz biorąc.

Ale co się dzieje w Twoim przypadku jest to:

m['A'] = m.size(); 

m jest std::map który tworzy nowy wpis, jeśli klucz nie istnieje.

W twoim przypadku klucz 'A' nie istnieje, więc tworzy wpis i zwraca referencję do wartości (która jest domyślnie utworzona), która następnie jest przypisana do Twojego przypadku w m.size().

Jak wspomniano powyżej, zachowanie jest nieokreślone, ponieważ kolejność oceny operandów jest nieokreślona, ​​co oznacza, że ​​m.size() może zostać oceniony przed m['A']. Jeśli tak, to m['A'] będzie 0, a nie 1.

+2

Dobrze, więc kolejność oceny operandów jest nieokreślona * ogólnie * w C++? –

+1

@EvgeniSergeev: Tak, dotyczy to wszystkich operatorów, ** oprócz ** operatora przecinka ',' w takim przypadku kolejność jest zdefiniowana jako od lewej do prawej. – Nawaz

+4

... a także dla operatorów 'lub' (' || '),' i' ('&&') również kolejność jest zdefiniowana. – iammilind

4

Ale wydaje się, że oceniając go po przypisaniu zaczęło ...

Kolejność oceny w zadania jest rzeczywiście nieokreślone (czy lewa ręka lub wyrażenie po prawej stronie jest oceniana pierwsza jest nieokreślona), jak wskazują odpowiedzi this question.

Jeśli najpierw oceniany jest m.size(), kod będzie działał zgodnie z przeznaczeniem, ale nie masz gwarancji tego zachowania, a inna implementacja może najpierw ocenić m['A'], taką samą sprawę z twoją. Tych dwuznacznych przypadków należy unikać.

Lepiej zrobić coś takiego zamiast

auto size = m.size(); 
m['A'] = size; 

które są gwarantowane, że zapytanie wielkość oceniana jest najpierw przed przypisania elementu.

LIVE CODE with the improvement..

4

nr

(§5.17/1): "We wszystkich przypadkach przypisanie jest sekwencjonowane po obliczeniu wartości prawego i lewego argumentu operacji, a przed obliczeniem wartości wyrażenia przypisania."

Należy jednak zauważyć, że chociaż przypisanie odbywa się po tym, jak prawy i lewy operandy są oceniane, nie jest ustalane kolejność między oceną lewego i prawego argumentu operacji. Dlatego lewą można ocenić najpierw, potem prawą lub odwrotnie.

1

Jest to przybliżona realizacja [] operatora edytowane z nagłówka pliku STL

mapped_type& operator[](const key_type& key){ 
auto itr = lower_bound(key); 
// itr->first is greater than or equivalent to key. 
if (itr == end() || comp_func(key, (*itr).first)) 
     itr = insert(itr, value_type(key, mapped_type())); 
return (*itr).second; 
} 

Więc jak widać dla nowego elementu wstawia pierwszy, a tym samym zwiększa rozmiar mapy przez 1

Ref std::map::operator[]

Jeśli klucz nie pasuje do klucza żadnego elementu w pojemniku,Funkcjawstawia nowy element z tym kluczem i zwraca odniesienie do jego zmapowanej wartości. Zauważ, że to zawsze zwiększa rozmiar pojemnika o jeden, nawet jeśli żadna zmapowana wartość nie jest przypisana do elementu (element jest konstruowany przy użyciu jego domyślnego konstruktora).

Edit:

Jak wskazano przez innych, m['A'] = m.size(); prowadzi do nieokreślonego zachowania, nigdy nie używać takich oświadczeń, zamiast tego można obliczyć wielkość, a następnie przypisać je do nowego klucza.