wartość domyślna std :: map

87

Czy istnieje sposób, aby określić wartość domyślną std::map„s operator[]powroty gdy klucz nie istnieje?

zaraz
źródło

Odpowiedzi:

57

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;
}
WhozCraig
źródło
1
Niezłe rozwiązanie. Możesz chcieć dodać kilka argumentów szablonu, aby szablon funkcji działał z mapami, które nie używają domyślnych parametrów szablonu dla komparatora i alokatora.
sbi
3
+1, ale aby zapewnić dokładnie to samo zachowanie, co operator[]z wartością domyślną, wartość domyślną należy wstawić do mapy wewnątrz if ( it == m.end() )bloku
David Rodríguez - dribeas
12
@David Zakładam, że OP tak naprawdę nie chce takiego zachowania. Używam podobnego schematu do odczytywania konfiguracji, ale nie chcę, aby konfiguracja była aktualizowana, jeśli brakuje klucza.
2
@GMan parametry bool są uważane przez niektórych za zły styl, ponieważ nie można powiedzieć, patrząc na wywołanie (w przeciwieństwie do deklaracji), co robią - w tym przypadku „prawda” oznacza „użyj domyślnego” lub „nie” t używać default "(lub czegoś zupełnie innego)? Wyliczenie jest zawsze jaśniejsze, ale oczywiście zawiera więcej kodu. Sam jestem w tej sprawie w dwóch głowach.
2
Ta odpowiedź nie działa, jeśli wartość domyślna to nullptr, ale stackoverflow.com/a/26958878/297451 tak.
Jon
44

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;
SurvivalMachine
źródło
1
To dla mnie najlepsze rozwiązanie. Łatwy do wdrożenia, bardzo elastyczny i ogólny.
acegs
11

Standard C ++ (23.3.1.2) określa, że ​​nowo wstawiona wartość jest skonstruowana domyślnie, więc mapsam w sobie nie zapewnia sposobu zrobienia tego. Masz do wyboru:

  • Podaj typowi wartości domyślny konstruktor, który inicjuje go do żądanej wartości lub
  • Zawiń mapę we własnej klasie, która zawiera wartość domyślną i implementuje operator[]wstawianie tej wartości domyślnej.
Mike Seymour
źródło
8
Cóż, żeby być precyzyjnym, nowo wstawiona wartość jest inicjalizowana wartością (8.5.5), więc: - jeśli T jest typem klasy z konstruktorem zadeklarowanym przez użytkownika (12.1), to wywoływany jest domyślny konstruktor dla T (a inicjalizacja jest źle -formowane, jeśli T nie ma dostępnego domyślnego konstruktora); - jeśli T nie jest typem klasy unii bez konstruktora zadeklarowanego przez użytkownika, to każdy niestatyczny element członkowski danych i składnik klasy bazowej T jest inicjowany wartością; - jeśli T jest typem tablicowym, to każdy element jest inicjalizowany wartością; - w przeciwnym razie obiekt jest inicjalizowany zerem
Tadeusz Kopec
6

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 dictrodzaju 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");
jyw
źródło
MAP :: mapped_type & nie jest bezpieczny do powrotu, ponieważ nazwa typu MAP :: mapped_type & defval może być poza zakresem.
Joe C
5

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.

Michael Anderson
źródło
2
Tak, aby uniknąć dodawania nowych wpisów, których możesz użyć, findktóre zwracają iterator końca, jeśli nie istnieje żaden element dla danego klucza.
Thomas Schaub,
@ThomasSchaub Jaka jest findw tym przypadku złożoność czasowa ?
ajaysinghnegi
5
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;
Thomas Eding
źródło
3
Następnie zmodyfikuj go, aby działał. Nie będę zawracał sobie głowy naprawianiem przypadku, do którego ten kod nie został zaprojektowany.
Thomas Eding
3

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.

świnia
źródło
2

Jednym obejściem jest użycie map::at()zamiast []. Jeśli klucz nie istnieje, atzgł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.

Dziekan
źródło
1

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;
VDVLeon
źródło
4
operator[]zwraca obiekt utworzony przez wywołanie T(), bez względu na to, co robi alokator.
sbi
1
@sbi: Czy mapa nie wywołuje constructmetody 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 ...
GManNickG
1
@GMan: moja kopia C ++ 03 mówi (w 23.3.1.2), że operator[]zwraca (*((insert(make_pair(x, T()))).first)).second. Więc jeśli czegoś nie brakuje, ta odpowiedź jest nieprawidłowa.
sbi
Masz rację. Ale wydaje mi się to niewłaściwe. Dlaczego nie używają funkcji alokatora do wstawiania?
VDVLeon
2
@sbi: Nie, zgadzam się, że ta odpowiedź jest nieprawidłowa, ale z innego powodu. Kompilator rzeczywiście robi to insertz a T(), ale wewnątrz insert jest wtedy, gdy użyje alokatora, pobierze pamięć dla nowego, Ta następnie wywoła constructtę 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 constructzignorowali jego parametr i użyli naszej specjalnej wartości, oznaczałoby to, że każdy skonstruowany element miał tę wartość, co jest złe.
GManNickG
1

Rozwijając odpowiedź https://stackoverflow.com/a/2333816/272642 , ta funkcja szablonu używa std::map's key_typei mapped_typetypedefs, aby wywnioskować typ keyi 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.

Vortico
źródło
1

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(). insertfaktycznie 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.

dhavenith
źródło