std :: map значение по умолчанию


89

Есть ли способ задать значение по умолчанию std::map«s operator[]возвращается , когда ключ не существует?

Ответы:


58

Нет, нет. Самое простое решение - написать для этого собственную бесплатную функцию-шаблон. Что-то вроде:

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

1
Хорошее решение. Возможно, вы захотите добавить несколько аргументов шаблона, чтобы шаблон функции работал с картами, которые не используют параметры шаблона по умолчанию для компаратора и распределителя.
sbi

3
+1, но для обеспечения того же поведения, что и operator[]значение по умолчанию, значение по умолчанию должно быть вставлено на карту внутри if ( it == m.end() )блока
Дэвид Родригес - dribeas

13
@David Я предполагаю, что OP на самом деле не хочет такого поведения. Я использую аналогичную схему для чтения конфигураций, но я не хочу, чтобы конфигурация обновлялась, если ключ отсутствует.

2
Некоторые считают параметры @GMan bool плохим стилем, потому что вы не можете сказать, посмотрев на вызов (в отличие от объявления), что они делают - в этом случае "true" означает "использовать по умолчанию" или "не делать" t use default "(или что-то совсем другое)? Перечисление всегда яснее, но, конечно, больше кода. Я сам по этому поводу двояко.

2
Этот ответ не работает, если значение по умолчанию равно nullptr, но работает stackoverflow.com/a/26958878/297451 .
Джон

45

Хотя это не совсем ответ на вопрос, я решил проблему с помощью такого кода:

struct IntDefaultedToMinusOne
{
    int i = -1;
};

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

1
Для меня это лучшее решение. Легко реализовать, очень гибко и универсально.
acegs 08

11

Стандарт C ++ (23.3.1.2) указывает, что вновь вставленное значение создается по умолчанию, поэтому mapсам по себе не предоставляет способа сделать это. Ваш выбор:

  • Дайте типу значения конструктор по умолчанию, который инициализирует его желаемым значением, или
  • Оберните карту в свой собственный класс, который предоставляет значение по умолчанию и реализует его operator[]для вставки.

8
Ну, чтобы быть точным, вновь вставленное значение инициализируется значением (8.5.5), поэтому: - если T является типом класса с конструктором, объявленным пользователем (12.1), то вызывается конструктор по умолчанию для T (и инициализация плохая -формируется, если у T нет доступного конструктора по умолчанию); - если T является типом класса без объединения без конструктора, объявленного пользователем, то каждый нестатический член данных и компонент базового класса T инициализируется значением; - если T является типом массива, то каждый элемент инициализируется значением; - в противном случае объект инициализируется нулем
Tadeusz Kopec


6

Более общая версия, поддержка 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");

MAP :: mapped_type & небезопасно возвращать, поскольку имя типа MAP :: mapped_type & defval может быть вне области видимости.
Joe C

5

Невозможно указать значение по умолчанию - это всегда значение, созданное по умолчанию (конструктор нулевого параметра).

Фактически, operator[]вероятно, делает больше, чем вы ожидаете, так как если значение не существует для данного ключа на карте, он вставит новое значение со значением из конструктора по умолчанию.


2
Правильно, чтобы избежать добавления новых записей, вы можете использовать, findкоторый возвращает конечный итератор, если для данного ключа не существует элемента.
Томас Шауб

@ThomasSchaub Какова временная сложность findв этом случае?
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
Затем измените его, чтобы он работал. Я не собираюсь исправлять случай, для которого этот код не предназначен.
Томас Эдинг

3

Значение инициализируется с использованием конструктора по умолчанию, как говорят другие ответы. Однако полезно добавить, что в случае простых типов (интегральные типы, такие как 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

См. Влияют ли круглые скобки после имени типа на новый? для получения более подробной информации по этому вопросу.


2

Один из способов решения этой проблемы - использовать map::at()вместо []. Если ключ не существует, atвыдает исключение. Еще лучше то, что это также работает для векторов и, таким образом, подходит для общего программирования, где вы можете поменять местами карту на вектор.

Использование настраиваемого значения для незарегистрированного ключа может быть опасным, поскольку это настраиваемое значение (например, -1) может обрабатываться дальше в коде. За исключением случаев, ошибки обнаруживать легче.


1

Может быть, вы можете предоставить настраиваемый распределитель, который будет выделять с желаемым значением по умолчанию.

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

4
operator[]возвращает объект, созданный при вызове T(), независимо от того, что делает распределитель.
sbi

1
@sbi: Разве карта не вызывает constructметод распределителей ? Думаю, это можно было бы изменить. Я подозреваю, что constructфункция, которая не new(p) T(t);занимается чем-то другим, не является правильно сформированной. РЕДАКТИРОВАТЬ: Оглядываясь назад, это было глупо, иначе все значения были бы одинаковыми: P Где мой кофе ...
GManNickG

1
@GMan: моя копия C ++ 03 говорит (в 23.3.1.2), что operator[]возвращается (*((insert(make_pair(x, T()))).first)).second. Поэтому, если я чего-то не упускаю, это неправильный ответ
SBI

Ты прав. Но мне это кажется неправильным. Почему они не используют функцию распределения для вставки?
VDVLeon

2
@sbi: Нет, я согласен, что это неверный ответ, но по другой причине. Компилятор действительно работает insertс a T(), но внутренняя вставка - это когда он будет использовать распределитель, получить память для нового, Tзатем вызвать constructэту память с заданным параметром, то есть T(). Таким образом, действительно можно изменить поведение, operator[]чтобы он возвращал что-то еще, но распределитель не может различить, почему он был вызван. Так что даже если бы мы constructигнорировали этот параметр и использовали наше специальное значение, это означало бы, что каждый сконструированный элемент имел это значение, что плохо.
GManNickG

1

Расширяя ответ https://stackoverflow.com/a/2333816/272642 , эта функция шаблона использует std::map's key_typeи mapped_typetypedefs для определения типа 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.


1

Используйте 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.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.