用于从常量映射中读取的惯用C++
Idiomatic C++ for reading from a const map
对于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++中没有方便、美观、标准的方法。我将不得不执行一项支持功能。
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;
}
};
- 理解boost::asio-async_read在无需读取内容时的行为
- 使用新行和不使用新行读取文件
- 读取文件并输入到矢量中
- 用c++从输入文件中读取另一行
- 读取文件的最后一行并输入到链接列表时出错
- 在进程中对同一管道进行读取和写入时C++管道出现问题
- 无法找到/读取配置文件.conf-FileIOException
- 如何使用Luacneneneba API正确读取字符串和表参数
- C++将文本文件中的数据读取到结构数组中
- 正在将csv文件读取为双精度矢量
- 为什么 sscanf 无法从一个字符串中读取uint64_t和字符?
- 为什么在读取文件大小时文件IO速度会发生变化
- 正在读取二进制文件(is_open)
- 如何在c++中从文本文件中逐行读取整数
- SSH通过/sbin/SSH无法读取RSA密钥文件(从控制台运行)
- 独立读取-修改-写入顺序
- 从文本文件中读取时钟时间和事件时间并进行处理
- 如何从文本文件中读取值和数组
- 为什么文件名被设置为一个点,而不是在读取矢量中的文件名时
- 一种在C++中读取TXT配置文件的简单方法