2010-02-25 2 views
52

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

+0

эта общая конструкция очень шикарно в Perl: 'моего $ Вэл = $ отображения {«ключ» }: «NAN» ' –

ответ

34

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

#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 Обновление

Цель: Счет для общих ассоциативных контейнеров, а также дополнительных компараторов и Allocator параметров.

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())' block –

+12

@David Я предполагаю, что OP на самом деле не хочет этого поведения. Я использую аналогичную схему для чтения конфигураций, но я не хочу, чтобы конфигурация обновлялась, если отсутствует ключ. – 2010-02-25 12:22:20

3

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

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

+2

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

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); 'не является корректной. EDIT: Оглядываясь назад, это было глупо, иначе все значения были бы одинаковыми: P Где мой кофе ... – GManNickG

+1

@GMan: моя копия C++ 03 говорит (в 23.3.1.2), что 'operator []' возвращает ' (* ((insert (make_pair (x, T()))) сначала)). second'. Поэтому, если я ничего не пропущу, этот ответ неверен. – sbi

10

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

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

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

4
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; 
+0

Попробуйте со строкой и буквальным. это не работает. –

+2

Затем измените его, чтобы он работал. Я не собираюсь исправлять случаи, когда этот код не был предназначен для выполнения. –

2

Значение инициализируется с помощью конструктора по умолчанию, как говорят другие ответы. Однако полезно добавить, что в случае простых типов (интегральные типы, такие как типы int, float, pointer или POD (планировать старые данные)), значения инициализируются нулями (или обнуляются инициализацией значения (что эффективно то же самое), в зависимости от того, какая версия 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 

См Do the parentheses after the type name make a difference with new? для получения более подробной информации по данному вопросу.

4

более общий вариант, Поддержка 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"); 
13

Хотя это не точно ответить на этот вопрос, я обойти эту проблему с кодом, как это:

struct IntDefaultedToMinusOne 
{ 
    int i = -1; 
}; 

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