用于从const映射中读取的惯用C ++

时间:2008-09-30 11:29:14

标签: c++ stl

对于std::map<std::string, std::string> variables,我想这样做:

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

唯一的问题是,在此上下文中variablesconst,因此operator[]无效:(

现在,有几种解决方法;抛弃const,使用variables.count("a") ? variables.find("a")->second : std::string()甚至制作包裹它的函数。在我看来,这些都不如operator[]那么好。我该怎么办?有没有一种标准的方法(精美)?

编辑:只是说出你们没有想要给出的答案:不,在C ++中没有方便,美观,标准的方法。我将不得不实施支持功能。

7 个答案:

答案 0 :(得分:11)

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

答案 1 :(得分:11)

抛弃const是错误的,因为地图上的operator []&lt;&gt;如果条目不存在默认构造字符串,则将创建该条目。如果地图实际上在不可变存储中,那么它将失败。这必须是这样的,因为operator []返回一个非const引用以允许赋值。 (例如,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;
}

如果我想到的话,我会考虑语法糖和更新。

...

立即语法糖包含一个自由函数,它执行map&lt;&gt; :: find()并返回一个包含map&lt;&gt; :: const_iterator的特殊类,然后重载operator ==()和operator! =()允许与映射类型进行比较。所以你可以这样做:

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

我不相信这比以前好多了:

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

它肯定要复杂得多,而且发生的事情就不那么明显了。

包装迭代器的对象的目的是停止使用默认的构造数据对象。如果您不在乎,那么只需使用“获取”答案。

为了回应关于比较优先的评论,希望找到未来的用途,我有这些评论:

  • 说出你的意思:调用名为“check_equal”的函数可以清楚地表明你在没有创建对象的情况下进行了相等比较。

  • 我建议您只在需要时才实现功能。在此之前做某事通常是个错误。

  • 根据具体情况,默认构造函数可能有副作用。如果您正在比较,为什么要做额外的事情呢?

  • SQL参数:NULL不等于空字符串。容器中是否缺少密钥与使用默认构造值的容器中存在的密钥完全相同?

说了这么多,默认构造对象相当于在非const容器上使用map&lt;&gt; :: operator []。也许你对get函数有一个当前的要求,它返回一个默认的构造对象;我知道我过去曾有这个要求。

答案 2 :(得分:5)

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

/编辑:正如Chris正确指出的那样,T 类型的对象的默认构造可能很昂贵,特别是因为即使实际上不需要这个对象也是如此(因为条目存在)。如果是这种情况,请不要在上述情况下使用def参数的默认值。

答案 3 :(得分:5)

有趣的是,在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

答案 4 :(得分:1)

实际上,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
}

可以在你正在使用的std :: map的子类中添加map::operator[]( const key_type& key ) const,并断言要找到的键,然后返回{{1} }。

答案 5 :(得分:0)

std::map<std::string, std::string>::const_iterator it( m.find("a") );
BOOST_CHECK_EQUAL( 
                     ( it == m.end() ? std::string("") : it->second ), 
                     "b" 
                 );

对我来说这看起来并不太糟糕......我可能不会为此写一个函数。

答案 6 :(得分:0)

跟随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;  
  }  
};