2009-12-20 2 views
29

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

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


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


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

ответ

31

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

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

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)); 

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

+0

Если у вас есть C++ 11 или выше, я рекомендую использовать список инициализаторов: 'm.insert ({k, v});'. Используйте 'V map :: at (K key)' для получения значения, например. 'int val = m.at (" important_value ")' – CJxD

1

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

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

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

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

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

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

+0

Все, что происходит, это задержка проблемы. Я хочу, чтобы шоу появилось еще раньше. Так как это происходит, мне не нужен флаг как объект по умолчанию seg-v, когда вы пытаетесь его использовать. – BCS

0

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

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

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

5

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

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

+2

Вполне нормально использовать нестандартные по умолчанию типы с std :: map - вы просто не можете использовать operator []. –

+0

Правда, спасибо! Включено в текст. – ariels

4

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

+0

re: по умолчанию всегда строится, это то, что делегаты, ленивая оценка и лямбда для :) – BCS

+0

В C++ 11 'V map :: at (K key)' работает с удовольствием. Сохраняет получение итератора и выполняет проверки. – CJxD

0

Вы можете специализировать 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-значение (& &).

1

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

что об использовании

map<K,V*> 

вместо

map<K,V> ? 
+0

лучше, чем карта будет map > –

+0

Без конструктора он не компилируется. Что касается использования V *, это будет своего рода счетчиком производительности, так как это еще больше ускорит обнаружение ошибок, и я попытаюсь сделать это раньше. Я пытаюсь сделать код компиляцией для случаев, которые никогда не вызовут конструктор по умолчанию и не скомпилируются для случаев, которые могли бы/могли бы вызвать его. – BCS

+0

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

3

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

+0

О, 'const', который может работать! – BCS

+4

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

+2

@ Jacek, если ваш производный класс не вводит никаких новых элементов данных, а его собственный деструктор пуст, это безопасно. –

2

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

+0

простой, чистый и работает плавно – DomTomCat