Подтвердить что ты не робот

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

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

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


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


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

4b9b3361

Ответ 1

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

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

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

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

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

даже если конструктор copy/move не поставляется.

Ответ 2

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

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

Ответ 3

Если тип значения не является конструктивным по умолчанию, тогда 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;
    }
}

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

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

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

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

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

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

Ответ 7

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

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

map<K,V*>

вместо

map<K,V> ?

Ответ 8

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

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

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

Ответ 9

вы можете специализировать 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) 
 {
   //...
 }

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

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

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