Идиоматический C ++ для чтения с карты const - PullRequest
14 голосов
/ 30 сентября 2008

Для std::map<std::string, std::string> variables я бы хотел сделать это:

BOOST_CHECK_EQUAL(variables["a"], "b");

Единственная проблема в этом контексте variables - это const, поэтому operator[] не будет работать: (

Теперь есть несколько способов обойти это; отбрасывая const, используя variables.count("a") ? variables.find("a")->second : std::string() или даже создавая функцию, упаковывающую это. Мне кажется, что все это не так хорошо, как operator[]. Что я должен делать? Есть ли стандартный способ сделать это (красиво)?

Редактировать: Просто констатировать ответ, который никто из вас не хочет дать: Нет, в C ++ нет удобного, красивого, стандартного способа сделать это. Я должен буду реализовать функцию поддержки.

Ответы [ 7 ]

11 голосов
/ 30 сентября 2008

Неверное отбрасывание const, потому что оператор [] на карте <> создаст запись, если она не присутствует с созданной по умолчанию строкой. Если карта на самом деле находится в неизменном хранилище, то она потерпит неудачу. Это должно быть так, потому что operator [] возвращает неконстантную ссылку для разрешения присваивания. (например, m [1] = 2)

Быстрая бесплатная функция для сравнения:

template<typename CONT>
bool check_equal(const CONT& m, const typename CONT::key_type& k,
                    const typename CONT::mapped_type& v)
{
    CONT::const_iterator i(m.find(k));
    if (i == m.end()) return false;
    return i->second == v;
}

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

...

Непосредственный синтаксический сахар включал свободную функцию, которая выполняет отображение <> :: find () и возвращает специальный класс, который упаковывает карту <> :: const_iterator, а затем перегруженный operator == () и operator! = ( ), чтобы разрешить сравнение с сопоставленным типом. Таким образом, вы можете сделать что-то вроде:

if (nonmutating_get(m, "key") == "value") { ... }

Я не уверен, что это намного лучше, чем:

if (check_equal(m, "key", "value")) { ... }

И это, конечно, намного сложнее, а то, что происходит, гораздо менее очевидно.

Целью обтекания объекта итератором является прекращение создания объектов данных по умолчанию. Если вам все равно, просто используйте ответ «get».

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

  • Скажите, что вы имеете в виду: вызов функции с именем "check_equal" дает понять, что вы выполняете сравнение на равенство без создания объекта.

  • Я рекомендую реализовывать функциональность только тогда, когда у вас есть такая необходимость. Делать что-то до этого часто является ошибкой.

  • В зависимости от ситуации, конструктор по умолчанию может иметь побочные эффекты. Если вы сравниваете, зачем делать что-то лишнее?

  • Аргумент SQL: NULL не эквивалентен пустой строке. Действительно ли отсутствие ключа в вашем контейнере совпадает с тем, что ключ присутствует в вашем контейнере с созданным по умолчанию значением?

Сказав все это, созданный по умолчанию объект эквивалентен использованию map <> :: operator [] в неконстантном контейнере. И, возможно, у вас есть текущее требование для функции get, которая возвращает созданный по умолчанию объект; Я знаю, что у меня было это требование в прошлом.

11 голосов
/ 30 сентября 2008
template <typename K, typename V>
V get(std::map<K, V> const& map, K const& key)
{
    std::map<K, V>::const_iterator iter(map.find(key));
    return iter != map.end() ? iter->second : V();
}

Улучшена реализация на основе комментариев:

template <typename T>
typename T::mapped_type get(T const& map, typename T::key_type const& key)
{
    typename T::const_iterator iter(map.find(key));
    return iter != map.end() ? iter->second : typename T::mapped_type();
}
5 голосов
/ 30 сентября 2008

Интересно отметить, что есть два способа найти тип шаблона в принятой реализации get (тот, который получает значение или возвращает построенный объект по умолчанию). Во-первых, вы можете сделать то, что было принято и иметь:

template <typename K, typename V>
V get1(const std::map<K, V>& theMap, const K const key)
{
    std::map<K, V>::const_iterator iter(theMap.find(key));
    return iter != theMap.end() ? iter->second : V();
}

или вы можете использовать тип карты и получить типы от этого:

template<typename T>
typename T::mapped_type
get2(const T& theMap, const typename T::key_type& key)
{
    typename T::const_iterator itr = theMap.find(key);
    return itr != theMap.end() ? itr->second : typename T::mapped_type();
}

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

std::map<std::string, int> data;
get1(data, "hey"); // doesn't compile because the key type is ambiguous
get2(data, "hey"); // just fine, a const char* can be converted to a string
5 голосов
/ 30 сентября 2008

find - идиоматическая форма. Отбрасывать const почти всегда плохая идея. Вы должны гарантировать, что операция записи не выполняется. Хотя этого можно ожидать от доступа для чтения на карте, в спецификации ничего об этом не сказано.

Если вы знаете , что значение существует, вы, конечно, можете отказаться от теста, используя count (что, во всяком случае, весьма неэффективно, так как это означает обход карты дважды. Даже если вы не знаете существует ли элемент, я бы не использовал. Вместо этого используйте следующее:

T const& item(map<TKey, T> const& m, TKey const& key, T const& def = T()) {
    map<TKey, T>::const_iterator i = m.find(key);
    return i == m.end() ? def : i->second;
}

/ EDIT: Как правильно указал Крис, конструкция по умолчанию для объектов типа T может быть дорогой, тем более что это делается, даже если этот объект на самом деле не нужен (поскольку запись существует). В этом случае не используйте значение по умолчанию для аргумента def в вышеприведенном случае.

1 голос
/ 30 сентября 2008

Действительно, operator [] является неконстантным на std :: map, поскольку он автоматически вставляет пару ключ-значение в карту, если ее там не было. (Ооо побочные эффекты!)

Правильный путь - использование map::find и, если возвращенный итератор действителен (!= map.end()), возвращает second, как вы показали.

map<int, int> m;
m[1]=5; m[2]=6; // fill in some couples
...
map<int,int>::const_iterator it = m.find( 3 );
if( it != m.end() ) {
    int value = it->second;
    // ... do stuff with value
}

Вы можете добавить map::operator[]( const key_type& key ) const в подкласс используемой вами std :: map и установить ключ, который будет найден, после чего вы вернете it->second.

0 голосов
/ 16 июня 2009

Следуя идее xtofl о специализации контейнера карты. Будет ли хорошо работать следующее?

template <typename K,typename V>  
struct Dictionary:public std::map<K,V>  
{  
  const V& operator[] (const K& key) const  
  {  
    std::map<K,V>::const_iterator iter(this->find(key));  
    BOOST_VERIFY(iter!=this->end());  
    return iter->second;  
  }  
};  
0 голосов
/ 30 сентября 2008
std::map<std::string, std::string>::const_iterator it( m.find("a") );
BOOST_CHECK_EQUAL( 
                     ( it == m.end() ? std::string("") : it->second ), 
                     "b" 
                 );

Это выглядит не так уж плохо для меня ... Я, вероятно, не написал бы функцию для этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...