从const映射读取的惯用C ++
对于std :: map <std :: string,std :: string>变量
,我想这样做:
BOOST_CHECK_EQUAL(variables["a"], "b");
唯一的问题是,在这种情况下variables
是const
,所以operator []
将不起作用:(
现在,有几种解决方法。使用variables.count(" a")抛弃
const吗? variables.find(" a")-> second:std :: string()
或者什至制作一个包装它的函数。在我看来,这些都不像" operator []"那样出色。我该怎么办?是否有一种标准的方法(美观)?
编辑:仅说明我们不想提供的答案:不,在C ++中没有方便,美观,标准的方法。我将必须实现支持功能。
解决方案
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:正如Chris正确指出的那样,类型为T的对象的默认构造可能会很昂贵,尤其是因为即使实际上不需要此对象(因为该条目存在),也可以这样做。如果是这种情况,请不要在上述情况下为def参数使用默认值。
实际上,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,并断言要找到的密钥,然后返回it-> second
。
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(); }
抛弃const是错误的,因为如果map <>上的operator []不包含默认构造的字符串,则它将创建该条目。如果映射实际上是在不可变的存储中,则它将失败。之所以必须这样,是因为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 <> :: find()并返回包装map <> :: const_iterator的特殊类,然后重载了operator ==()和operator!=()以允许与映射类型的比较。因此,我们可以执行以下操作:
if (nonmutating_get(m, "key") == "value") { ... }
我不认为这比以下要好得多:
if (check_equal(m, "key", "value")) { ... }
当然,它要复杂得多,发生的事情却不那么明显。
包装迭代器的对象的目的是停止具有默认构造的数据对象。如果我们不在乎,则只需使用"获取"答案即可。
为了回应关于希望获得优先于比较的评论,以期找到将来的用途,我有以下评论:
- 说出意思:调用一个名为" check_equal"的函数可以清楚地表明,我们在进行相等比较时无需创建对象。
- 我建议仅在需要时才实施功能。在此之前做某事通常是一个错误。
- 根据情况,默认构造函数可能会有副作用。如果我们正在比较,为什么还要做些额外的事情?
- SQL参数:NULL不等于空字符串。容器中缺少密钥是否真的与容器中存在的具有默认构造值的密钥相同?
说了这么多,默认构造的对象等效于在非const容器上使用map <> :: operator []。也许我们当前对get函数的要求是,该函数返回默认的构造对象;我知道我过去曾经有过这样的要求。
有趣的是,有两种方法可以在接受的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<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; } };