std :: map valore predefinito


89

C'è un modo per specificare std::mapi operator[]ritorni del valore predefinito quando una chiave non esiste?

Risposte:


58

No, non c'è. La soluzione più semplice è scrivere la tua funzione template gratuita per farlo. Qualcosa di simile a:

#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 );
}

Aggiornamento C ++ 11

Scopo: tenere conto di contenitori associativi generici, nonché parametri di confronto e allocatori opzionali.

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;
}

1
Bella soluzione. Potresti voler aggiungere alcuni argomenti del modello in modo che il modello della funzione funzioni con le mappe che non utilizzano i parametri del modello predefinito per il comparatore e l'allocatore.
sbi

3
+1, ma per fornire lo stesso identico comportamento del operator[]valore predefinito, il valore predefinito dovrebbe essere inserito nella mappa all'interno del if ( it == m.end() )blocco
David Rodríguez - dribeas

13
@ David, presumo che l'OP non voglia effettivamente quel comportamento. Uso uno schema simile per leggere le configurazioni, ma non voglio che la configurazione venga aggiornata se manca una chiave.

2
I parametri bool @GMan sono ritenuti da alcuni un cattivo stile perché non puoi dire guardando la chiamata (al contrario della dichiarazione) cosa fanno - in questo caso "true" significa "usa default" o "don ' t usare default "(o qualcos'altro completamente)? Un enum è sempre più chiaro ma ovviamente è più codice. Sono in due menti sull'argomento, me stesso.

2
Questa risposta non funziona se il valore predefinito è nullptr, ma stackoverflow.com/a/26958878/297451 sì .
Jon,

45

Anche se questo non risponde esattamente alla domanda, ho aggirato il problema con un codice come questo:

struct IntDefaultedToMinusOne
{
    int i = -1;
};

std::map<std::string, IntDefaultedToMinusOne > mymap;

1
Questa è la soluzione migliore per me. Facile da implementare, molto flessibile e generico.
acegs

11

Lo standard C ++ (23.3.1.2) specifica che il valore appena inserito è costruito per impostazione predefinita, quindi di per mapsé non fornisce un modo per farlo. Le tue scelte sono:

  • Assegna al tipo di valore un costruttore predefinito che lo inizializza al valore desiderato, o
  • Avvolgi la mappa nella tua classe che fornisce un valore predefinito e implementa operator[]per inserire tale valore predefinito.

8
Bene, per essere precisi il valore appena inserito è il valore inizializzato (8.5.5) quindi: - se T è un tipo di classe con un costruttore dichiarato dall'utente (12.1), viene chiamato il costruttore predefinito per T (e l'inizializzazione è malata -formato se T non ha un costruttore predefinito accessibile); - se T è un tipo di classe non union senza un costruttore dichiarato dall'utente, ogni membro di dati non statici e componente di classe base di T è inizializzato con valore; - se T è un tipo array, ogni elemento è inizializzato con valore; - in caso contrario, l'oggetto è inizializzato a zero
Tadeusz Kopec


6

Versione più generale, supporto C ++ 98/03 e più contenitori

Funziona con contenitori associativi generici, l'unico parametro del modello è il tipo di contenitore stesso.

Contenitori supportati: 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;
}

Utilizzo:

std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");

Ecco un'implementazione simile utilizzando una classe wrapper, che è più simile al metodo get()di dicttipo in Python: 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);
}

Utilizzo:

std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");

MAP :: mapped_type & non è sicuro da restituire poiché typename MAP :: mapped_type & defval può essere fuori dall'ambito.
Joe C

5

Non è possibile specificare il valore predefinito: è sempre un valore costruito dal valore predefinito (costruttore di parametri zero).

In effetti operator[]probabilmente fa più di quanto ti aspetti, poiché se non esiste un valore per la chiave data nella mappa ne inserirà uno nuovo con il valore del costruttore predefinito.


2
Bene, per evitare di aggiungere nuove voci potresti usare findche restituisce l'iteratore di fine se non esiste alcun elemento per una data chiave.
Thomas Schaub,

@ThomasSchaub Quanto costa la complessità temporale findin quel caso?
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;

3
Quindi modificalo in modo che funzioni. Non mi prenderò la briga di risolvere un caso per cui questo codice non è stato progettato.
Thomas Eding

3

Il valore viene inizializzato utilizzando il costruttore predefinito, come dicono le altre risposte. Tuttavia, è utile aggiungere che in caso di tipi semplici (tipi integrali come int, float, pointer o POD (plan old data)), i valori sono inizializzati a zero (o azzerati dall'inizializzazione del valore (che è effettivamente la stessa cosa), a seconda della versione di C ++ utilizzata).

Ad ogni modo, la conclusione è che le mappe con tipi semplici azzereranno automaticamente i nuovi elementi. Quindi, in alcuni casi, non è necessario preoccuparsi di specificare esplicitamente il valore iniziale predefinito.

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

Vedi Le parentesi dopo il nome del tipo fanno la differenza con new? per maggiori dettagli in merito.


2

Una soluzione alternativa è usare map::at()invece di []. Se una chiave non esiste, atgenera un'eccezione. Ancora più bello, funziona anche per i vettori, ed è quindi adatto per la programmazione generica in cui è possibile scambiare la mappa con un vettore.

L'utilizzo di un valore personalizzato per una chiave non registrata può essere pericoloso poiché tale valore personalizzato (come -1) può essere elaborato più in basso nel codice. Con le eccezioni, è più facile individuare i bug.


1

Forse puoi dare un allocatore personalizzato che alloca con un valore predefinito che desideri.

template < class Key, class T, class Compare = less<Key>,
       class Allocator = allocator<pair<const Key,T> > > class map;

4
operator[]restituisce un oggetto creato invocando T(), indipendentemente da ciò che fa l'allocatore.
sbi

1
@sbi: la mappa non chiama il constructmetodo degli allocatori ? Sarebbe possibile cambiarlo, credo. Sospetto che una constructfunzione che non faccia altro che new(p) T(t);non sia ben formata, però. EDIT: Col senno di poi è stato sciocco, altrimenti tutti i valori sarebbero gli stessi: P Dov'è il mio caffè ...
GManNickG

1
@GMan: la mia copia di C ++ 03 dice (in 23.3.1.2) che operator[]ritorna (*((insert(make_pair(x, T()))).first)).second. Quindi, a meno che non mi manchi qualcosa, questa risposta è sbagliata.
sbi

Hai ragione. Ma a me sembra sbagliato. Perché non usano la funzione allocatore per l'inserimento?
VDVLeon,

2
@sbi: No, sono d'accordo che questa risposta sia sbagliata, ma per un motivo diverso. Il compilatore in effetti fa insertcon un T(), ma all'interno dell'inserimento è quando userà l'allocatore per ottenere memoria per un nuovo Tquindi chiamerà constructquella memoria con il parametro che è stato dato, che è T(). Quindi è effettivamente possibile modificare il comportamento di operator[]per fare in modo che restituisca qualcos'altro, ma l'allocatore non può differenziare il motivo per cui viene chiamato. Quindi, anche se facessimo constructignorare il suo parametro e usassimo il nostro valore speciale, ciò significherebbe che ogni elemento costruito aveva quel valore, il che è negativo.
GManNickG

1

Ampliando la risposta https://stackoverflow.com/a/2333816/272642 , questo modello utilizza la funzione std::maps' key_typee mapped_typetypedef di dedurre il tipo di keye def. Questo non funziona con i contenitori senza questi typedef.

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;
}

Questo ti permette di usare

std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);

senza bisogno di lanciare argomenti come std::string("a"), (int*) NULL.


1

Usa std::map::insert().

Rendendosi conto che sono abbastanza in ritardo per questa festa, ma se sei interessato al comportamento di operator[]con impostazioni predefinite personalizzate (ovvero, trova l'elemento con la chiave data, se non è presente inserisci un elemento nella mappa con un scelti valore predefinito e restituire un riferimento a uno il valore appena inserito o il valore esistente), c'è già una funzione a vostra disposizione pre C ++ 17: std::map::insert(). inserteffettivamente non inserirà se la chiave esiste già, ma restituirà invece un iteratore al valore esistente.

Supponiamo che tu volessi una mappa di string-to-int e inserisca un valore predefinito di 42 se la chiave non era ancora presente:

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;
}

che dovrebbero produrre 42, 43 e 44.

Se il costo di costruzione del valore della mappa è alto (se copiare / spostare la chiave o il tipo di valore è costoso), ciò comporta una significativa riduzione delle prestazioni, che penso sarebbe aggirata con C ++ 17 try_emplace.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.