std :: значення за промовчанням


87

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

Відповіді:


57

Ні, немає. Найпростішим рішенням є написання власної функції безкоштовного шаблону для цього. Щось на зразок:

#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

12
@David Я припускаю, що OP насправді не хоче такої поведінки. Я використовую подібну схему для читання конфігурацій, але не хочу, щоб конфігурація оновлювалася, якщо ключ відсутній.

2
Деякі параметри bool @GMan вважаються поганим стилем, оскільки ви не можете зрозуміти, дивлячись на виклик (на відміну від декларації), що вони роблять - у цьому випадку "true" означає "використовувати за замовчуванням" або "don ' t використовувати за замовчуванням "(або щось інше цілком)? Перелік завжди чіткіший, але, звичайно, це більше коду. Я сам у двох думках з цього питання.

2
Ця відповідь не працює , якщо значення за замовчуванням nullptr, але stackoverflow.com/a/26958878/297451 робить.
Джон

44

Хоча це точно не відповідає на питання, я вирішив проблему з таким кодом:

struct IntDefaultedToMinusOne
{
    int i = -1;
};

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

1
Це найкраще рішення для мене. Простий у впровадженні, дуже гнучкий та загальний.
acegs

11

Стандарт C ++ (23.3.1.2) вказує, що щойно вставлене значення побудовано за замовчуванням, тому mapсам не надає способу це зробити. Ваш вибір:

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

8
Ну, а якщо бути точніше, нещодавно вставлене значення - це значення, ініціалізоване (8.5.5), отже: - якщо T - це тип класу із заявленим користувачем конструктором (12.1), тоді викликається конструктор за замовчуванням для T (і ініціалізація погана -формується, якщо T не має доступного конструктора за замовчуванням); - якщо T не є об'єднаним типом класу без конструктора, оголошеного користувачем, тоді кожен нестатичний член даних та компонент базового класу T ініціалізуються за значенням; - якщо T - тип масиву, то кожен елемент ініціалізується значенням; - інакше об'єкт ініціалізований нулем
Тадеуш Копець

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 & не безпечно повертати, оскільки ім'я type MAP :: mapped_type & defval може бути поза сферою дії.
Joe C


5

Неможливо вказати значення за замовчуванням - це завжди значення, побудоване за замовчуванням (конструктор нульових параметрів).

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


2
Правильно, щоб уникнути додавання нових записів, які ви можете використовувати, findякі повертають кінцевий ітератор, якщо для даного ключа не існує елемента.
Thomas Schaub

@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, покажчик або типи 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

Див. Чи мають дужки після назви типу різницю з новими? для детальної інформації.


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

Ти правий. Але це здається мені неправильним. Чому вони не використовують функцію розподільника для вставки?
VDVЛеон

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

1

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


1

Використовуйте 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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.