Чи є спосіб задати значення за замовчуванням 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 ++ 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");
C ++ 17 забезпечує, try_emplace
що саме це робить. Він бере ключ і список аргументів для конструктора значень і повертає пару: an iterator
і a bool
.: Http://en.cppreference.com/w/cpp/container/map/try_emplace
Неможливо вказати значення за замовчуванням - це завжди значення, побудоване за замовчуванням (конструктор нульових параметрів).
Насправді, 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, покажчик або типи 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
Див. Чи мають дужки після назви типу різницю з новими? для детальної інформації.
Одним з обхідних шляхів є використання 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
' key_type
і mapped_type
typedefs для виведення типу key
та def
. Це не працює з контейнерами без цих 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;
}
Це дозволяє використовувати
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
насправді не буде вставляти, якщо ключ уже існує, а замість цього повертати ітератор до існуючого значення.
Скажімо, ви хотіли карту string-to-int і вставляєте значення за замовчуванням 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
.