Использование std:: map, где V не имеет используемого конструктора по умолчанию


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

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


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


Edit: несколько человек предложили решение, которое сводится к изменению типа значения, чтобы карта могла построить его без вызова конструктора по умолчанию. это имеет совершенно противоположный результат того, что я хочу, потому что он скрывает ошибку до более позднего времени. Если бы я был готов к этому, я мог бы просто удалить assert из конструктора. Чего я хочу , так это сделайте так, чтобы ошибка произошла еще раньше, во время компиляции. Однако, похоже, что нет никакого способа отличить R-value от L-value использования operator[] , поэтому кажется, что то, что я хочу, не может быть сделано, поэтому мне просто придется обойтись без использования всего этого вместе.

9 34

9 ответов:

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

Лично я никогда не использую оператор [] для карт ни для чего, кроме быстрого и грязного демонстрационного кода. Вместо этого используйте 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) );

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

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

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

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

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

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

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

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

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

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

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

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

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

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

map<K,V*>

Вместо

map<K,V> ?

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

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

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

Вы можете специализировать std:: map для вашего типа значения. Я не говорю, что это хорошая идея, но это можно сделать. Я специализировал scoped_ptr<FILE> s 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) 
 {
   //...
 }

Это должно позволить вам вставить код, который вы хотите в operator[] для вашего типа. К сожалению, я не знаю способа в текущем 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 (&&).