Есть ли способ задать значение по умолчанию std::map
«s operator[]
возвращается , когда ключ не существует?
Ответы:
Нет, нет. Самое простое решение - написать для этого собственную бесплатную функцию-шаблон. Что-то вроде:
#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
именно это. Он принимает ключ и список аргументов для конструктора значения и возвращает пару: an iterator
и a bool
.: 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.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);
}
Применение:
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
с a T()
, но внутренняя вставка - это когда он будет использовать распределитель, получить память для нового, T
затем вызвать construct
эту память с заданным параметром, то есть T()
. Таким образом, действительно можно изменить поведение, operator[]
чтобы он возвращал что-то еще, но распределитель не может различить, почему он был вызван. Так что даже если бы мы construct
игнорировали этот параметр и использовали наше специальное значение, это означало бы, что каждый сконструированный элемент имел это значение, что плохо.
Расширяя ответ https://stackoverflow.com/a/2333816/272642 , эта функция шаблона использует std::map
's key_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
.