Gibt es eine Möglichkeit , den Standardwert angeben std::map
‚s operator[]
zurückgibt , wenn ein Schlüssel nicht existiert?
Antworten:
Nein, gibt es nicht. Die einfachste Lösung besteht darin, eine eigene kostenlose Vorlagenfunktion zu schreiben, um dies zu tun. Etwas wie:
#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 );
}
C ++ 11 Update
Zweck: Berücksichtigen Sie generische assoziative Container sowie optionale Komparator- und Allokatorparameter.
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[]
mit dem Standardwert bereitzustellen , sollte der Standardwert in die Karte innerhalb des if ( it == m.end() )
Blocks eingefügt werden
Dies beantwortet zwar nicht genau die Frage, aber ich habe das Problem mit Code wie diesem umgangen:
struct IntDefaultedToMinusOne
{
int i = -1;
};
std::map<std::string, IntDefaultedToMinusOne > mymap;
Der C ++ - Standard (23.3.1.2) gibt an, dass der neu eingefügte Wert standardmäßig erstellt wird, sodass er map
selbst keine Möglichkeit bietet, dies zu tun. Sie haben folgende Möglichkeiten:
operator[]
, um diesen Standard einzufügen.Allgemeinere Version, Unterstützung von C ++ 98/03 und mehr Containern
Funktioniert mit generischen assoziativen Containern. Der einzige Vorlagenparameter ist der Containertyp selbst.
Unterstützte Behälter: std::map
, std::multimap
, std::unordered_map
, std::unordered_multimap
, wxHashMap
, QMap
, QMultiMap
, QHash
, QMultiHash
, usw.
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;
}
Verwendung:
std::map<int, std::string> t;
t[1] = "one";
string s = get_with_default(t, 2, "unknown");
Hier ist eine ähnliche Implementierung unter Verwendung einer Wrapper-Klasse, die get()
der dict
Typmethode in Python ähnlicher ist : 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);
}
Verwendung:
std::map<int, std::string> t;
t[1] = "one";
string s = wrap_map(t).get(2, "unknown");
C ++ 17 bietet try_emplace
genau das. Es nimmt einen Schlüssel und eine Argumentliste für den Wertekonstruktor und gibt ein Paar zurück: an iterator
und a bool
.: Http://en.cppreference.com/w/cpp/container/map/try_emplace
Es gibt keine Möglichkeit, den Standardwert anzugeben - es handelt sich immer um einen vom Standard (Nullparameter-Konstruktor) erstellten Wert.
Tatsächlich macht es operator[]
wahrscheinlich mehr als Sie erwarten, als ob ein Wert für den angegebenen Schlüssel in der Zuordnung nicht existiert. Es wird ein neuer mit dem Wert aus dem Standardkonstruktor eingefügt.
find
die den Enditerator zurückgeben, wenn für einen bestimmten Schlüssel kein Element vorhanden ist.
find
in diesem Fall die zeitliche Komplexität ?
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;
Der Wert wird mit dem Standardkonstruktor initialisiert, wie in den anderen Antworten angegeben. Es ist jedoch nützlich hinzuzufügen, dass bei einfachen Typen (integrale Typen wie int, float, pointer oder POD (Plan Old Data) -Typen) die Werte auf Null initialisiert (oder durch Wertinitialisierung auf Null gesetzt) werden (was effektiv ist das gleiche), abhängig davon, welche Version von C ++ verwendet wird).
Unter dem Strich werden Karten mit einfachen Typen die neuen Elemente automatisch auf Null initialisieren. In einigen Fällen müssen Sie sich also nicht darum kümmern, den Standardanfangswert explizit anzugeben.
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
Siehe Machen die Klammern nach dem Typnamen einen Unterschied zu new? für weitere Details zu diesem Thema.
Eine Problemumgehung ist die Verwendung von map::at()
anstelle von []
. Wenn kein Schlüssel vorhanden ist, wird at
eine Ausnahme ausgelöst. Noch schöner ist, dass dies auch für Vektoren funktioniert und sich daher für die generische Programmierung eignet, bei der Sie die Karte gegen einen Vektor austauschen können.
Die Verwendung eines benutzerdefinierten Werts für nicht registrierte Schlüssel kann gefährlich sein, da dieser benutzerdefinierte Wert (wie -1) möglicherweise weiter unten im Code verarbeitet wird. Mit Ausnahmen ist es einfacher, Fehler zu erkennen.
Möglicherweise können Sie einen benutzerdefinierten Zuweiser angeben, der einen gewünschten Standardwert zuweist.
template < class Key, class T, class Compare = less<Key>,
class Allocator = allocator<pair<const Key,T> > > class map;
operator[]
Gibt ein durch Aufrufen erstelltes Objekt zurück T()
, unabhängig davon, was der Allokator tut.
construct
? Das könnte man ändern, denke ich. Ich vermute jedoch eine construct
Funktion, die nichts anderes tut als new(p) T(t);
nicht gut geformt ist. EDIT: Im Nachhinein war das dumm, sonst wären alle Werte gleich: P Wo ist mein Kaffee ...
operator[]
zurückgegeben wird (*((insert(make_pair(x, T()))).first)).second
. Wenn mir also nichts fehlt, ist diese Antwort falsch.
insert
mit a T()
, aber innerhalb von insert wird der Allokator verwendet, um Speicher für einen neuen Speicher T
aufzurufen construct
und diesen Speicher mit dem angegebenen Parameter aufzurufen T()
. Es ist also tatsächlich möglich, das Verhalten zu ändern operator[]
, damit es etwas anderes zurückgibt, aber der Allokator kann nicht unterscheiden, warum es aufgerufen wird. Selbst wenn wir construct
den Parameter ignorieren und unseren speziellen Wert verwenden würden, würde dies bedeuten, dass jedes konstruierte Element diesen Wert hat, was schlecht ist.
Verwenden Sie std::map::insert()
.
Wenn Sie operator[]
feststellen, dass ich zu spät zu dieser Party komme, aber wenn Sie sich für das Verhalten mit benutzerdefinierten Standardeinstellungen interessieren (dh das Element mit dem angegebenen Schlüssel suchen, wenn es nicht vorhanden ist, fügen Sie ein Element mit einem in die Karte ein Standardwert gewählt und einen Verweis entweder auf den neu eingefügten Wert oder auf den vorhandenen Wert zurückgegeben). Vor C ++ 17 steht Ihnen bereits eine Funktion zur Verfügung : std::map::insert()
. insert
wird nicht eingefügt, wenn der Schlüssel bereits vorhanden ist, sondern einen Iterator auf den vorhandenen Wert zurücksetzen.
Angenommen, Sie wollten eine Zuordnung von String zu Int und fügen einen Standardwert von 42 ein, wenn der Schlüssel noch nicht vorhanden war:
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;
}
welches 42, 43 und 44 ausgeben sollte.
Wenn die Kosten für die Erstellung des Kartenwerts hoch sind (wenn entweder das Kopieren / Verschieben des Schlüssels oder der Werttyp teuer ist), führt dies zu einem erheblichen Leistungsverlust, der meiner Meinung nach mit C ++ 17 umgangen werden würde try_emplace
.
Diese Vorlagenfunktion erweitert die Antwort https://stackoverflow.com/a/2333816/272642 und verwendet std::map
's key_type
und mapped_type
typedefs, um den Typ von key
und abzuleiten def
. Dies funktioniert nicht mit Containern ohne diese Typedefs.
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;
}
Dies ermöglicht Ihnen die Verwendung
std::map<std::string, int*> m;
int* v = getWithDefault(m, "a", NULL);
ohne die Argumente wie zu werfen std::string("a"), (int*) NULL
.