Zidentyfikowałem cztery różne sposoby wstawiania elementów do std::map
:
std::map<int, int> function;
function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));
Który z nich jest preferowany / idiomatyczny? (Czy jest inny sposób, o którym nie pomyślałem?)
Odpowiedzi:
Przede wszystkim,
operator[]
iinsert
funkcje składowe nie są funkcjonalnie równoważne:operator[]
Będzie szukać klucza, włóż domyślny skonstruowana wartość, jeśli nie znaleziono, i zwraca referencję do których można przypisać wartość. Oczywiście może to być nieefektywne, jeślimapped_type
może skorzystać z bezpośredniego zainicjowania zamiast domyślnego konstruowania i przypisywania. Ta metoda uniemożliwia również określenie, czy wstawienie rzeczywiście miało miejsce, czy też nadpisałeś tylko wartość dla wcześniej wstawionego kluczainsert
członkowska nie odniesie żadnego skutku, jeśli klucz jest już obecny na mapie i chociaż często o nim zapomina, zwraca wartość,std::pair<iterator, bool>
która może być interesująca (przede wszystkim w celu ustalenia, czy wstawienie zostało faktycznie wykonane).Ze wszystkich wymienionych możliwości dzwonienia
insert
wszystkie trzy są prawie równoważne. Dla przypomnienia spójrzmy nainsert
podpis w standardzie:Więc czym różnią się te trzy rozmowy?
std::make_pair
opiera się na dedukcji argumentów szablonu i może (iw tym przypadku będzie ) wytwarzać coś innego typu niż rzeczywistavalue_type
mapa, co będzie wymagało dodatkowego wywołaniastd::pair
konstruktora szablonu w celu konwersji navalue_type
(tj: dodawanieconst
dofirst_type
)std::pair<int, int>
będzie również wymagało dodatkowego wywołania konstruktora szablonustd::pair
w celu przekonwertowania parametru navalue_type
(np .: dodanieconst
dofirst_type
)std::map<int, int>::value_type
nie pozostawia żadnych wątpliwości, ponieważ jest to bezpośrednio typ parametru oczekiwany przezinsert
funkcję składową.Ostatecznie unikałbym używania,
operator[]
gdy celem jest wstawienie, chyba że nie ma dodatkowych kosztów w domyślnym konstruowaniu i przypisywaniumapped_type
, oraz że nie obchodzi mnie ustalanie, czy nowy klucz został skutecznie włożony. Podczas używaniainsert
, konstruowanievalue_type
jest prawdopodobnie najlepszym rozwiązaniem.źródło
Od C ++ 11 masz dwie główne dodatkowe opcje. Po pierwsze, możesz użyć
insert()
ze składnią inicjalizacji listy:Jest to funkcjonalnie równoważne z
ale o wiele bardziej zwięzłe i czytelne. Jak zauważyły inne odpowiedzi, ma to kilka zalet w porównaniu z innymi formami:
operator[]
Podejście wymaga rodzaj zmapowanych być przypisane, co nie zawsze jest prawdą.operator[]
podejście może nadpisać istniejące elementy i nie daje możliwości stwierdzenia, czy tak się stało.insert
, które wymienisz, obejmują niejawną konwersję typów, która może spowolnić kod.Główną wadą jest to, że kiedyś ten formularz wymagał kopiowania klucza i wartości, więc nie działałby np. Z mapą z
unique_ptr
wartościami. Zostało to naprawione w standardzie, ale poprawka może nie dotarła jeszcze do implementacji standardowej biblioteki.Po drugie, możesz skorzystać z
emplace()
metody:Jest to bardziej zwięzłe niż którakolwiek z form programu
insert()
, działa dobrze z typami tylko do przenoszeniaunique_ptr
, i teoretycznie może być nieco bardziej wydajne (chociaż przyzwoity kompilator powinien zoptymalizować różnicę). Jedyną poważną wadą jest to, że może to trochę zaskoczyć czytelników, ponieważemplace
metody nie są zwykle używane w ten sposób.źródło
Pierwsza wersja:
może, ale nie musi, wstawić wartość 42 do mapy. Jeśli klucz
0
istnieje, przypisze 42 do tego klucza, nadpisując jakąkolwiek wartość, jaką miał ten klucz. W przeciwnym razie wstawia parę klucz / wartość.Funkcje wstawiania:
z drugiej strony nie rób nic, jeśli klucz
0
już istnieje na mapie. Jeśli klucz nie istnieje, wstawia parę klucz / wartość.Trzy funkcje wstawiania są prawie identyczne.
std::map<int, int>::value_type
jesttypedef
forstd::pair<const int, int>
istd::make_pair()
oczywiście produkujestd::pair<>
magię dedukcji za pomocą szablonu. Jednak wynik końcowy powinien być taki sam dla wersji 2, 3 i 4.Którego bym użył? Osobiście wolę wersję 1; jest zwięzła i „naturalna”. Oczywiście, jeśli jego nadpisywanie nie jest pożądane, wolałbym wersję 4, ponieważ wymaga mniej wpisywania niż wersje 2 i 3. Nie wiem, czy istnieje jeden de facto sposób wstawiania par klucz / wartość do
std::map
.Inny sposób wstawiania wartości do mapy za pomocą jednego z jej konstruktorów:
źródło
Jeśli chcesz nadpisać element kluczem 0
Inaczej:
źródło
Od C ++ 17
std::map
oferuje dwie nowe metody wstawiania:insert_or_assign()
itry_emplace()
, jak również wspomniano w komentarzu sp2danny .insert_or_assign()
Zasadniczo
insert_or_assign()
jest to „ulepszona” wersjaoperator[]
. W przeciwieństwie dooperator[]
,insert_or_assign()
nie wymaga, aby typ wartości mapy był domyślnym konstruowalnym. Na przykład poniższy kod nie jest kompilowany, ponieważMyClass
nie ma domyślnego konstruktora:Jeśli jednak zastąpisz
myMap[0] = MyClass(1);
ją następującym wierszem, kod zostanie skompilowany, a wstawienie nastąpi zgodnie z zamierzeniami:Ponadto, podobnie jak
insert()
,insert_or_assign()
zwracapair<iterator, bool>
. Wartość logiczna dotyczytrue
sytuacji, w której nastąpiło wstawienie ifalse
jeśli przypisanie zostało wykonane. Iterator wskazuje na element, który został wstawiony lub zaktualizowany.try_emplace()
Podobnie jak powyżej,
try_emplace()
jest „ulepszeniem”emplace()
. W przeciwieństwie doemplace()
,try_emplace()
nie modyfikuje swoich argumentów, jeśli wstawianie nie powiedzie się z powodu klucza już istniejącego w mapie. Na przykład poniższy kod próbuje umieścić element z kluczem, który jest już przechowywany w mapie (patrz *):Wyjście (przynajmniej dla VS2017 i Coliru):
Jak widać,
pMyObj
nie wskazuje już na oryginalny obiekt. Jeśli jednak zastąpiszauto [it, b] = myMap2.emplace(0, std::move(pMyObj));
poniższym kodem, dane wyjściowe będą wyglądać inaczej, ponieważpMyObj
pozostają niezmienione:Wynik:
Kod na Coliru
Uwaga: starałem się, aby moje wyjaśnienia były jak najkrótsze i najprostsze, aby dopasować je do tej odpowiedzi. Aby uzyskać bardziej precyzyjny i wyczerpujący opis, polecam przeczytanie tego artykułu na temat Fluent C ++ .
źródło
Dokonałem porównań czasowych między w / w wersjami:
Okazuje się, że różnice czasowe między wersjami wkładek są niewielkie.
To daje odpowiednio dla wersji (uruchomiłem plik 3 razy, stąd 3 kolejne różnice czasu dla każdej):
2198 ms, 2078 ms, 2072 ms
2290 ms, 2037 ms, 2046 ms
2592 ms, 2278 ms, 2296 ms
2234 ms, 2031 ms, 2027 ms
W związku z tym można pominąć wyniki między różnymi wersjami wkładek (chociaż nie przeprowadzono testu hipotezy)!
map_W_3[it] = Widget(2.0);
Wersja trwa około 10-15% więcej czasu na ten przykład ze względu na inicjalizacji z konstruktora domyślnego dla widgetu.źródło
Krótko mówiąc,
[]
operator jest bardziej wydajny w aktualizowaniu wartości, ponieważ polega na wywołaniu domyślnego konstruktora typu wartości, a następnie przypisaniu mu nowej wartości, podczas gdyinsert()
jest bardziej wydajny w dodawaniu wartości.Cytowany fragment z Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library autorstwa Scotta Meyersa może pomóc.
Możesz zdecydować się na wybranie wersji wolnej od programowania ogólnego, ale chodzi o to, że uważam ten paradygmat (rozróżnienie „dodaj” i „zaktualizuj”) za niezwykle przydatny.
źródło
Jeśli chcesz wstawić element do std :: map - użyj funkcji insert (), a jeśli chcesz znaleźć element (po klawiszu) i przypisać do niego jakiś - użyj operatora [].
Aby uprościć wstawianie, użyj biblioteki boost :: assign, na przykład:
źródło
Po prostu trochę zmieniam problem (mapa strun), aby pokazać inne zainteresowanie insertem:
fakt, że kompilator nie wyświetla błędu na "rancking [1] = 42;" może mieć niszczycielski wpływ!
źródło
std::string::operator=(char)
istnieje, ale pokazują błąd dla drugiego, ponieważ konstruktorstd::string::string(char)
nie istnieje. Nie powinno to powodować błędu, ponieważ C ++ zawsze swobodnie interpretuje dowolny literał typu całkowitego jakochar
, więc nie jest to błąd kompilatora, ale zamiast tego jest to błąd programisty. Zasadniczo mówię tylko, że to, czy powoduje to błąd w kodzie, jest czymś, na co musisz uważać. BTW, możesz drukować,rancking[0]
a kompilator używający ASCII wypisze*
, czyli(char)(42)
.