C'è un modo per specificare std::map
i operator[]
ritorni del valore predefinito quando una chiave non esiste?
Risposte:
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;
}
operator[]
valore predefinito, il valore predefinito dovrebbe essere inserito nella mappa all'interno del if ( it == m.end() )
blocco
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;
Lo standard C ++ (23.3.1.2) specifica che il valore appena inserito è costruito per impostazione predefinita, quindi di per map
sé non fornisce un modo per farlo. Le tue scelte sono:
operator[]
per inserire tale valore predefinito.C ++ 17 fornisce try_emplace
che fa esattamente questo. Richiede una chiave e un elenco di argomenti per il costruttore di valori e restituisce una coppia: an iterator
e a bool
.: Http://en.cppreference.com/w/cpp/container/map/try_emplace
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 dict
tipo 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");
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.
find
che restituisce l'iteratore di fine se non esiste alcun elemento per una data chiave.
find
in quel caso?
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;
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.
Una soluzione alternativa è usare map::at()
invece di []
. Se una chiave non esiste, at
genera 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.
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;
operator[]
restituisce un oggetto creato invocando T()
, indipendentemente da ciò che fa l'allocatore.
construct
metodo degli allocatori ? Sarebbe possibile cambiarlo, credo. Sospetto che una construct
funzione 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è ...
operator[]
ritorna (*((insert(make_pair(x, T()))).first)).second
. Quindi, a meno che non mi manchi qualcosa, questa risposta è sbagliata.
insert
con un T()
, ma all'interno dell'inserimento è quando userà l'allocatore per ottenere memoria per un nuovo T
quindi chiamerà construct
quella 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 construct
ignorare il suo parametro e usassimo il nostro valore speciale, ciò significherebbe che ogni elemento costruito aveva quel valore, il che è negativo.
Ampliando la risposta https://stackoverflow.com/a/2333816/272642 , questo modello utilizza la funzione std::map
s' key_type
e mapped_type
typedef di dedurre il tipo di key
e 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
.
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()
. insert
effettivamente 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
.