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

Idiomatic C++ for reading from a const map

本文关键字:C++ 读取 常量 映射 用于      更新时间:2023-10-16

对于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++中没有方便、美观、标准的方法。我将不得不执行一项支持功能。

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

去掉常量是错误的,因为映射上的运算符[]<>如果条目没有默认构造的字符串,则将创建该条目。如果映射实际上在不可变存储中,那么它将失败。这一定是因为运算符[]返回了一个允许赋值的非常数引用。(例如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(),并返回一个特殊的类来包装map<>::const_iterator,然后具有重载运算符==()和运算符=()以允许与映射的类型进行比较。所以你可以做一些类似的事情:

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

我不相信这比好多少

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

它当然要复杂得多,正在发生的事情也不那么明显。

对象包装迭代器的目的是停止使用默认构造的数据对象。如果你不在乎,那就用"得到"这个答案。

为了回应关于get比比较更受欢迎的评论,我有以下评论:

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

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

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

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

话虽如此,默认构造的对象相当于使用map<>:运算符[]。也许您当前需要一个返回默认构造对象的get函数;我知道我过去也有这样的要求。

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参数的默认值。

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

事实上,运算符[]在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::operator[]( const key_type& key ) const,并断言要找到的密钥,然后返回it->second

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

这对我来说并不算太糟……我可能不会为此编写函数。

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