14

对于一个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++ 中没有方便、美观、标准的方法来做到这一点。我将不得不实现一个支持功能。

4

7 回答 7

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();
}
于 2008-09-30T11:38:08.240 回答
11

抛弃 const 是错误的,因为 map<> 上的 operator[] 将创建该条目,如果它不存在默认构造的字符串。如果地图实际上是在不可变存储中,那么它将失败。必须如此,因为 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;
}

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

...

直接的语法糖涉及一个自由函数,它执行 map<>::find() 并返回一个包装 map<>::const_iterator 的特殊类,然后重载 operator==() 和 operator!=() 以允许与映射类型进行比较。因此,您可以执行以下操作:

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

我不相信这比:

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

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

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

为了回应关于 get 比比较更受欢迎的评论,希望能找到一些未来的用途,我有以下评论:

  • 说出您的意思:调用一个名为“check_equal”的函数可以清楚地表明您正在进行相等比较而无需创建对象。

  • 我建议仅在您有需要时才实施功能。在此之前做某事往往是错误的。

  • 根据情况,默认构造函数可能会产生副作用。如果你在比较,为什么要做额外的事情?

  • SQL 参数:NULL 不等于空字符串。容器中缺少键是否与容器中存在具有默认构造值的键真的相同?

说了这么多,默认构造的对象等效于在非常量容器上使用 map<>::operator[] 。也许您当前需要一个返回默认构造对象的 get 函数;我知道我过去有过这样的要求。

于 2008-09-30T11:45:23.900 回答
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;
}

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

于 2008-09-30T11:36:10.460 回答
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
于 2008-09-30T14:03:58.733 回答
1

实际上,operator[] 是 std::map 上的一个非常量,因为如果它不存在,它会自动在映射中插入一个键值对。(哦哦副作用!)

正确的方法是使用map::findand,如果返回的迭代器有效 ( != 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 的子类中添加 a map::operator[]( const key_type& key ) const,并断言要找到的键,然后返回it->second.

于 2008-09-30T11:36:45.263 回答
0
std::map<std::string, std::string>::const_iterator it( m.find("a") );
BOOST_CHECK_EQUAL( 
                     ( it == m.end() ? std::string("") : it->second ), 
                     "b" 
                 );

这对我来说看起来还不错……我可能不会为此编写函数。

于 2008-09-30T16:40:00.650 回答
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;  
  }  
};  
于 2009-06-16T07:51:41.670 回答