Есть ли способ задать значение по умолчанию std::map
«s operator[]
возвращается , когда ключ не существует?
89
Нет, нет. Самое простое решение - написать для этого собственную бесплатную функцию-шаблон. Что-то вроде:
#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
Цель: учитывать общие ассоциативные контейнеры, а также дополнительные параметры компаратора и распределителя.
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[]
значение по умолчанию, значение по умолчанию должно быть вставлено на карту внутриif ( it == m.end() )
блокаХотя это не совсем ответ на вопрос, я решил проблему с помощью такого кода:
struct IntDefaultedToMinusOne { int i = -1; }; std::map<std::string, IntDefaultedToMinusOne > mymap;
источник
Стандарт C ++ (23.3.1.2) указывает, что вновь вставленное значение создается по умолчанию, поэтому
map
сам по себе не предоставляет способа сделать это. Ваш выбор:operator[]
для вставки.источник
C ++ 17 предоставляет
try_emplace
именно это. Он принимает ключ и список аргументов для конструктора значения и возвращает пару: aniterator
и abool
.: Http://en.cppreference.com/w/cpp/container/map/try_emplaceисточник
Более общая версия, поддержка C ++ 98/03 и других контейнеров
Работает с универсальными ассоциативными контейнерами, единственный параметр шаблона - это сам тип контейнера.
Поддерживаемые контейнеры:
std::map
,std::multimap
,std::unordered_map
,std::unordered_multimap
,wxHashMap
,QMap
,QMultiMap
,QHash
,QMultiHash
, и т.д.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; }
Применение:
std::map<int, std::string> t; t[1] = "one"; string s = get_with_default(t, 2, "unknown");
Вот подобная реализация с помощью класса - оболочки, который больше похож на метод
get()
изdict
типа в Python: https://github.com/hltj/wxMEdit/blob/master/src/xm/xm_utils.hpptemplate<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); }
Применение:
std::map<int, std::string> t; t[1] = "one"; string s = wrap_map(t).get(2, "unknown");
источник
Невозможно указать значение по умолчанию - это всегда значение, созданное по умолчанию (конструктор нулевого параметра).
Фактически,
operator[]
вероятно, делает больше, чем вы ожидаете, так как если значение не существует для данного ключа на карте, он вставит новое значение со значением из конструктора по умолчанию.источник
find
который возвращает конечный итератор, если для данного ключа не существует элемента.find
в этом случае?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;
источник
Значение инициализируется с использованием конструктора по умолчанию, как говорят другие ответы. Однако полезно добавить, что в случае простых типов (интегральные типы, такие как int, float, pointer или POD (plan old data)) значения инициализируются нулем (или обнуляются путем инициализации значения (что эффективно то же самое), смотря какая версия C ++ используется).
В любом случае, суть в том, что карты с простыми типами автоматически обнуляют новые элементы. Поэтому в некоторых случаях не нужно беспокоиться о явном указании начального значения по умолчанию.
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
См. Влияют ли круглые скобки после имени типа на новый? для получения более подробной информации по этому вопросу.
источник
Один из способов решения этой проблемы - использовать
map::at()
вместо[]
. Если ключ не существует,at
выдает исключение. Еще лучше то, что это также работает для векторов и, таким образом, подходит для общего программирования, где вы можете поменять местами карту на вектор.Использование настраиваемого значения для незарегистрированного ключа может быть опасным, поскольку это настраиваемое значение (например, -1) может обрабатываться дальше в коде. За исключением случаев, ошибки обнаруживать легче.
источник
Может быть, вы можете предоставить настраиваемый распределитель, который будет выделять с желаемым значением по умолчанию.
template < class Key, class T, class Compare = less<Key>, class Allocator = allocator<pair<const Key,T> > > class map;
источник
operator[]
возвращает объект, созданный при вызовеT()
, независимо от того, что делает распределитель.construct
метод распределителей ? Думаю, это можно было бы изменить. Я подозреваю, чтоconstruct
функция, которая неnew(p) T(t);
занимается чем-то другим, не является правильно сформированной. РЕДАКТИРОВАТЬ: Оглядываясь назад, это было глупо, иначе все значения были бы одинаковыми: P Где мой кофе ...operator[]
возвращается(*((insert(make_pair(x, T()))).first)).second
. Поэтому, если я чего-то не упускаю, это неправильный ответinsert
с aT()
, но внутренняя вставка - это когда он будет использовать распределитель, получить память для нового,T
затем вызватьconstruct
эту память с заданным параметром, то естьT()
. Таким образом, действительно можно изменить поведение,operator[]
чтобы он возвращал что-то еще, но распределитель не может различить, почему он был вызван. Так что даже если бы мыconstruct
игнорировали этот параметр и использовали наше специальное значение, это означало бы, что каждый сконструированный элемент имел это значение, что плохо.Расширяя ответ https://stackoverflow.com/a/2333816/272642 , эта функция шаблона использует
std::map
'skey_type
иmapped_type
typedefs для определения типаkey
иdef
. Это не работает с контейнерами без этих определений типов.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; }
Это позволяет использовать
std::map<std::string, int*> m; int* v = getWithDefault(m, "a", NULL);
без необходимости приводить такие аргументы, как
std::string("a"), (int*) NULL
.источник
Используйте
std::map::insert()
.Понимая, что я довольно опаздываю на эту вечеринку, но если вас интересует поведение
operator[]
с пользовательскими значениями по умолчанию (то есть найти элемент с заданным ключом, если его нет вставьте элемент на карту с выбрано значение по умолчанию , и возвращает ссылку на любой из вновь вставленного значения или существующее значение), Существует уже функция доступна вам предварительно C ++ 17:std::map::insert()
.insert
фактически не будет вставлять, если ключ уже существует, а вместо этого вернет итератор к существующему значению.Скажем, вам нужна карта преобразования строки в целое число, и вы вставляете значение по умолчанию 42, если ключ еще не присутствовал:
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; }
который должен вывести 42, 43 и 44.
Если стоимость построения значения карты высока (если копирование / перемещение ключа или типа значения является дорогостоящим), это приводит к значительному снижению производительности, что, как я думаю, можно обойти с помощью C ++ 17
try_emplace
.источник