Czy istnieje sposób, aby określić wartość domyślną std::map
„s operator[]
powroty gdy klucz nie istnieje?
Odpowiedzi:
Nie, nie ma. Najprostszym rozwiązaniem jest napisanie własnej, darmowej funkcji szablonu, aby to zrobić. Coś jak:
#include <string>
#include <map>
using namespace std;
template <typename K, typename V>
V GetWithDef(const std::map <K,V> & m, const K & key, const V & defval ) {
typename std::map<K,V>::const_iterator it = m.find( key );
if ( it == m.end() ) {
return defval;
}
else {
return it->second;
}
}
int main() {
map <string,int> x;
...
int i = GetWithDef( x, string("foo"), 42 );
}
Aktualizacja C ++ 11
Cel: uwzględnienie ogólnych kontenerów asocjacyjnych, a także opcjonalnych parametrów komparatora i alokatora.
template <template<class,class,class...> class C, typename K, typename V, typename... Args>
V GetWithDef(const C<K,V,Args...>& m, K const& key, const V & defval)
{
typename C<K,V,Args...>::const_iterator it = m.find( key );
if (it == m.end())
return defval;
return it->second;
}
operator[]
z wartością domyślną, wartość domyślną należy wstawić do mapy wewnątrz if ( it == m.end() )
bloku
Chociaż nie jest to dokładna odpowiedź na pytanie, omijałem problem z kodem w następujący sposób:
struct IntDefaultedToMinusOne
{
int i = -1;
};
std::map<std::string, IntDefaultedToMinusOne > mymap;
Standard C ++ (23.3.1.2) określa, że nowo wstawiona wartość jest skonstruowana domyślnie, więc map
sam w sobie nie zapewnia sposobu zrobienia tego. Masz do wyboru:
operator[]
wstawianie tej wartości domyślnej.Bardziej ogólna wersja, obsługa C ++ 98/03 i więcej kontenerów
Działa z generycznymi kontenerami asocjacyjnymi, jedynym parametrem szablonu jest sam typ kontenera.
Obsługiwane pojemniki: std::map
, std::multimap
, std::unordered_map
, std::unordered_multimap
, wxHashMap
, QMap
, QMultiMap
, QHash
, QMultiHash
, etc.
template<typename MAP>
const typename MAP::mapped_type& get_with_default(const MAP& m,
const typename MAP::key_type& key,
const typename MAP::mapped_type& defval)
{
typename MAP::const_iterator it = m.find(key);
if (it == m.end())
return defval;
return it->second;
}
Stosowanie:
std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");
Tutaj jest podobna realizacja za pomocą klasy otoki, który jest bardziej podobny do sposobu get()
z dict
rodzaju w Pythonie: https://github.com/hltj/wxMEdit/blob/master/src/xm/xm_utils.hpp
template<typename MAP>
struct map_wrapper
{
typedef typename MAP::key_type K;
typedef typename MAP::mapped_type V;
typedef typename MAP::const_iterator CIT;
map_wrapper(const MAP& m) :m_map(m) {}
const V& get(const K& key, const V& default_val) const
{
CIT it = m_map.find(key);
if (it == m_map.end())
return default_val;
return it->second;
}
private:
const MAP& m_map;
};
template<typename MAP>
map_wrapper<MAP> wrap_map(const MAP& m)
{
return map_wrapper<MAP>(m);
}
Stosowanie:
std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");
C ++ 17 zapewnia try_emplace
dokładnie to. Pobiera klucz i listę argumentów dla konstruktora wartości i zwraca parę: an iterator
i a bool
.: Http://en.cppreference.com/w/cpp/container/map/try_emplace
Nie ma możliwości określenia wartości domyślnej - jest to zawsze wartość skonstruowana domyślnie (konstruktor z zerowymi parametrami).
W rzeczywistości operator[]
prawdopodobnie robi więcej, niż się spodziewasz, ponieważ jeśli wartość dla danego klucza w mapie nie istnieje, wstawi nowy z wartością z domyślnego konstruktora.
find
które zwracają iterator końca, jeśli nie istnieje żaden element dla danego klucza.
find
w tym przypadku złożoność czasowa ?
template<typename T, T X>
struct Default {
Default () : val(T(X)) {}
Default (T const & val) : val(val) {}
operator T & () { return val; }
operator T const & () const { return val; }
T val;
};
<...>
std::map<KeyType, Default<ValueType, DefaultValue> > mapping;
Wartość jest inicjowana przy użyciu domyślnego konstruktora, jak mówią inne odpowiedzi. Warto jednak dodać, że w przypadku typów prostych (typy całkowite, takie jak int, float, pointer lub POD (plan old data)), wartości są inicjalizowane przez zero (lub zerowane przez inicjalizację wartości (co jest efektywnie to samo), w zależności od używanej wersji C ++).
W każdym razie, podstawową kwestią jest to, że mapy z prostymi typami automatycznie wyzerują nowe elementy. Dlatego w niektórych przypadkach nie ma potrzeby martwić się jawnym określeniem domyślnej wartości początkowej.
std::map<int, char*> map;
typedef char *P;
char *p = map[123],
*p1 = P(); // map uses the same construct inside, causes zero-initialization
assert(!p && !p1); // both will be 0
Zobacz Czy nawiasy po nazwie typu mają znaczenie w przypadku nowego? aby uzyskać więcej informacji na ten temat.
Jednym obejściem jest użycie map::at()
zamiast []
. Jeśli klucz nie istnieje, at
zgłasza wyjątek. Co więcej, działa to również dla wektorów, a zatem jest odpowiednie do programowania ogólnego, w którym można zamienić mapę na wektor.
Używanie wartości niestandardowej dla niezarejestrowanego klucza może być niebezpieczne, ponieważ ta wartość niestandardowa (np. -1) może być przetwarzana dalej w kodzie. Z wyjątkami łatwiej jest wykryć błędy.
Może możesz dać niestandardowego alokatora, który przydziela z domyślną wartością, którą chcesz.
template < class Key, class T, class Compare = less<Key>,
class Allocator = allocator<pair<const Key,T> > > class map;
operator[]
zwraca obiekt utworzony przez wywołanie T()
, bez względu na to, co robi alokator.
construct
metody alokatorów ? Myślę, że można by to zmienić. Podejrzewam jednak, construct
że funkcja, która robi coś innego niż new(p) T(t);
nie jest dobrze sformułowana. EDYCJA: Z perspektywy czasu to było głupie, w przeciwnym razie wszystkie wartości byłyby takie same: P Gdzie moja kawa ...
operator[]
zwraca (*((insert(make_pair(x, T()))).first)).second
. Więc jeśli czegoś nie brakuje, ta odpowiedź jest nieprawidłowa.
insert
z a T()
, ale wewnątrz insert jest wtedy, gdy użyje alokatora, pobierze pamięć dla nowego, T
a następnie wywoła construct
tę pamięć z podanym parametrem, którym jest T()
. Tak więc rzeczywiście można zmienić zachowanie, operator[]
aby zwracał coś innego, ale alokator nie może rozróżnić, dlaczego jest wywoływany. Więc nawet gdybyśmy construct
zignorowali jego parametr i użyli naszej specjalnej wartości, oznaczałoby to, że każdy skonstruowany element miał tę wartość, co jest złe.
Rozwijając odpowiedź https://stackoverflow.com/a/2333816/272642 , ta funkcja szablonu używa std::map
's key_type
i mapped_type
typedefs, aby wywnioskować typ key
i def
. Nie działa to z kontenerami bez tych typów definicji.
template <typename C>
typename C::mapped_type getWithDefault(const C& m, const typename C::key_type& key, const typename C::mapped_type& def) {
typename C::const_iterator it = m.find(key);
if (it == m.end())
return def;
return it->second;
}
Pozwala to na użycie
std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);
bez konieczności rzucania argumentów takich jak std::string("a"), (int*) NULL
.
Użyj std::map::insert()
.
Zdając sobie sprawę, że jestem dość spóźniony na tę imprezę, ale jeśli interesuje Cię zachowanie operator[]
z niestandardowymi ustawieniami domyślnymi (to znaczy znajdź element z podanym kluczem, jeśli go nie ma wstaw element na mapie z wybrana wartość domyślną i powrót odniesienie do każdej nowo wstawionego wartości lub istniejącej wartości), nie jest już dostępny do ciebie funkcja pre C ++ 17: std::map::insert()
. insert
faktycznie nie wstawi, jeśli klucz już istnieje, ale zamiast tego zwróci iterator do istniejącej wartości.
Powiedzmy, że potrzebujesz mapy ciągów do liczby int i wstawia domyślną wartość 42, jeśli klucza jeszcze nie było:
std::map<std::string, int> answers;
int count_answers( const std::string &question)
{
auto &value = answers.insert( {question, 42}).first->second;
return value++;
}
int main() {
std::cout << count_answers( "Life, the universe and everything") << '\n';
std::cout << count_answers( "Life, the universe and everything") << '\n';
std::cout << count_answers( "Life, the universe and everything") << '\n';
return 0;
}
co powinno dać wynik 42, 43 i 44.
Jeśli koszt konstruowania wartości mapy jest wysoki (jeśli kopiowanie / przenoszenie klucza lub typ wartości jest drogi), wiąże się to ze znacznym spadkiem wydajności, co moim zdaniem można by obejść w C ++ 17 try_emplace
.