Использование std :: map <K, V>, где V не имеет используемого конструктора по умолчанию - PullRequest
41 голосов
/ 20 декабря 2009

У меня есть таблица символов, реализованная как std::map. Для значения нет способа легитимно создать экземпляр типа значения через конструктор по умолчанию. Однако, если я не предоставляю конструктор по умолчанию, я получаю ошибку компилятора, и если я заставляю конструктор утверждать, моя программа компилируется очень хорошо, но падает внутри map<K,V>::operator [], если я пытаюсь использовать его для добавления нового члена. *

Есть ли способ заставить C ++ запретить map[k] как значение l во время компиляции (при этом разрешив его как значение r)?


Кстати: я знаю, что могу вставить в карту, используя Map.insert(map<K,V>::value_type(k,v)).


Редактировать: Несколько человек предложили решение, которое сводится к изменению типа значения, чтобы карта могла создать его без вызова конструктора по умолчанию. Это имеет совершенно противоположный результат того, что я хочу , потому что это скрывает ошибку до позже. Если бы я захотел это сделать, я мог бы просто удалить утверждение из конструктора. Я хочу , чтобы ошибка произошла еще раньше; во время компиляции. Тем не менее, кажется, что нет никакого способа разграничить использование значения 10 и значения r для operator[], поэтому, кажется, что я не могу этого сделать, поэтому мне просто придется отказаться от использования всего этого вместе.

Ответы [ 9 ]

41 голосов
/ 20 декабря 2009

Вы не можете заставить компилятор различать два использования оператора [], потому что это одно и то же. Оператор [] возвращает ссылку, поэтому версия присвоения просто присваивается этой ссылке.

Лично я никогда не использую оператор [] для карт для чего-либо кроме быстрого и грязного демонстрационного кода. Вместо этого используйте insert () и find (). Обратите внимание, что функция make_pair () облегчает использование insert:

m.insert( make_pair( k, v ) );

В C ++ 11 вы также можете сделать

m.emplace( k, v );
m.emplace( piecewise_construct, make_tuple(k), make_tuple(the_constructor_arg_of_v) );

, даже если конструктор копирования / перемещения не предоставлен.

5 голосов
/ 20 декабря 2009

Ваш V не имеет конструктора по умолчанию, поэтому вы не можете ожидать, что std::map<K,V> std::map<K,V>::operator[] будет использоваться.

A std::map<K, boost::optional<V> > имеет значение mapped_type, которое конструируется по умолчанию и, вероятно, имеет семантику, которую вы хотите. За подробностями обращайтесь к документации Boost.Optional (вам нужно будет знать о них).

4 голосов
/ 20 декабря 2009

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

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

например:

template <class K, class V>
V& get(std::map<K, V>& m, const K& k)
{
    typename std::map<K, V>::iterator it = m.find(k);
    if (it != m.end()) {
        return it->second;
    }
    throw std::range_error("Missing key");
}

template <class K, class V>
const V& get(const std::map<K, V>& m, const K& k)
{
    typename std::map<K, V>::const_iterator it = m.find(k);
    if (it != m.end()) {
        return it->second;
    }
    throw std::range_error("Missing key");
}

template <class K, class V>
void set(std::map<K, V>& m, const K& k, const V& v)
{
    std::pair<typename std::map<K, V>::iterator,bool> result = m.insert(std::make_pair(k, v));
    if (!result.second) {
        result.first->second = v;
    }
}

Вы также можете рассмотреть метод получения, подобный dict.get(key [, default]) в Python (который возвращает предоставленное значение по умолчанию, если ключ отсутствует), но при этом возникает проблема с удобством использования в том, что значение по умолчанию всегда должно быть создано, даже если вы знаете, что ключ на карте).

3 голосов
/ 22 декабря 2009

Получите новый класс из std::map<K,V> и создайте свой собственный operator[]. Пусть он возвращает константную ссылку, которую нельзя использовать как значение l.

2 голосов
/ 09 марта 2017

Используйте map<K,V>::at(). map<K,V>::operator [] попытается создать элемент по умолчанию, если предоставленный ключ еще не существует.

1 голос
/ 20 декабря 2009

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

как насчет использования

map<K,V*>

вместо

map<K,V> ?
1 голос
/ 20 декабря 2009

Это немного уродливо, но один из способов обойти это - добавить переменную-член, которая отслеживает, является ли экземпляр действительным или нет. Ваш конструктор по умолчанию помечает экземпляр как недействительный, но все остальные ваши конструкторы помечают его как допустимый.

Убедитесь, что ваш оператор присваивания правильно передает новую переменную-член.

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

Измените все остальные ваши функции-члены, чтобы они выдавали / ошиблись / утверждали, когда они работают с недействительным экземпляром.

Затем вы можете использовать свой объект на карте, и если вы используете только объекты, которые были правильно построены, ваш код будет работать нормально.

Опять же, это обходной путь, если вы хотите использовать карту STL и не хотите использовать вставку и поиск вместо оператора [].

0 голосов
/ 20 декабря 2009

вы можете специализировать std :: map для вашего значения-типа. Я не говорю, что это хорошая идея, но это можно сделать. Я специализировал scoped_ptr<FILE> dtor на fclose вместо delete.

Что-то вроде:

 template<class K, class Compare, class Allocator>
 my_value_type& std::map<K,my_value_type,Compare,Allocator>::operator[](const K& k) 
 {
   //...
 }

Это должно позволить вам вставить нужный код в оператор [] для вашего типа. К сожалению, я не знаю, как в текущем c ++ вернуть только r значений. В C ++ 0x вы можете использовать:

 template<class K, class Compare, class Allocator>
 my_value_type&& std::map<K,my_value_type,Compare,Allocator>::operator[](const K& k) 
 {
   //...
 }

Это вернет ссылку на R-значение (&&).

0 голосов
/ 20 декабря 2009

Когда вы используете переопределение оператора в C ++, лучше всего придерживаться семантики оператора в случае по умолчанию. Семантика по умолчанию. Оператор [] является заменой существующего члена в массиве. Казалось бы, std :: map немного отклоняет правила. Это прискорбно, потому что это приводит к такого рода путанице.

Обратите внимание, что документация (http://www.sgi.com/tech/stl/Map.html) для оператора [] в std :: map гласит: «Возвращает ссылку на объект, связанный с определенным ключом. Если карта еще не содержит такого объекта , operator [] вставляет объект по умолчанию data_type (). "

Я бы посоветовал вам по-разному относиться к замене и вставке. К сожалению, это означает, что вам нужно знать, что требуется. Это может означать сначала поиск на карте. Если производительность является проблемой, вам может потребоваться найти оптимизацию, где вы можете проверить членство и вставить с одним поиском.

...