[英]range-based for loop for private map values
我有以下代码:
#include "stdafx.h"
#include <map>
#include <string>
#include <iostream>
class MyObject
{
public:
MyObject()
: m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
{}
RETURNTYPE GetStringIterator() const
{
IMPLEMENTATION
}
private:
std::map<int, std::string> m_Items;
};
int main()
{
MyObject o;
for (auto& s : o.GetStringIterator())
{
std::cout << s;
}
}
RETURNTYPE
和IMPLEMENTATION
应该允许MyObject
任何客户端(在本例中为main()
函数)迭代m_Items
映射的值,而不复制任何数据? 似乎这应该可以使用基于c ++ 11范围的循环和迭代器。 但我无法弄清楚如何。
基于范围的迭代可以像这样实现:
class MyObject
{
public:
MyObject()
: m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
{}
auto begin() { return m_Items.begin(); }
auto begin() const { return m_Items.begin(); }
auto end() { return m_Items.end(); }
auto end() const { return m_Items.end(); }
private:
std::map<int, std::string> m_Items;
};
复制或不复制值取决于代码在调用站点的写入方式:
MyObject a;
for(auto [key,value] : a) {} // copies are made
for(auto & [key,value] : a) {} // no copy
for(auto const & [key,value] : a) {} // no copy
您可以通过删除begin
和end
的非const版本来禁用地图值的修改:
class MyObject
{
public:
MyObject()
: m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
{}
auto begin() const { return m_Items.begin(); }
auto end() const { return m_Items.end(); }
private:
std::map<int, std::string> m_Items;
};
然后,尝试修改范围for for循环中的值将导致编译错误:
MyObject a;
for(auto & [key,value] : a) {
//value.push_back('a'); // Not OK
}
for(auto & [key,value] : a) {
cout << value; // OK
}
请注意,如果地图是实现细节,则应使用@Barry提出的答案,因为它仅迭代地图的值,而不是关键字。
你可以使用boost::adaptors::map_values
,它适用于C ++ 11:
auto GetStringIterator() const
// NB: have the move the declaration of m_Items ahead of this function for this to work
-> decltype(m_Items | boost::adaptors::map_values)
{
return m_Items | boost::adaptors::map_values;
}
或者它的range-v3等价, view::values
。 两者都可以像values(m)
而不是m | values
m | values
,如果你喜欢那样的话。
两种解决方案都会将视图返回到地图的值。 这是一个不拥有任何底层元素的对象,复制起来很便宜 - 即O(1)。 我们不是在对地图或其任何底层元素进行编码。
您可以使用它,就好像它是任何其他范围:
for (std::string const& s : o.GetStringIterator()) {
// ...
}
此循环不会复制任何字符串。 每个s
直接指向map
存储的相应string
。
我将在c ++ 14中首先回答这个问题。
这是一个最小的映射iteratoroid:
template<class F, class It>
struct iterator_mapped {
decltype(auto) operator*() const {
return f(*it);
}
iterator_mapped( F f_in, It it_in ):
f(std::move(f_in)),
it(std::move(it_in))
{}
iterator_mapped( iterator_mapped const& ) = default;
iterator_mapped( iterator_mapped && ) = default;
iterator_mapped& operator=( iterator_mapped const& ) = default;
iterator_mapped& operator=( iterator_mapped && ) = default;
iterator_mapped& operator++() {
++it;
return *this;
}
iterator_mapped operator++(int) {
auto copy = *this;
++*this;
return copy;
}
friend bool operator==( iterator_mapped const& lhs, iterator_mapped const& rhs ) {
return lhs.it == rhs.it;
}
friend bool operator!=( iterator_mapped const& lhs, iterator_mapped const& rhs ) {
return !(lhs==rhs);
}
private:
F f;
It it;
};
它在技术上不是一个迭代器,但它有资格for(:)
循环。
template<class It>
struct range_t {
It b, e;
It begin() const { return b; }
It end() const { return e; }
};
template<class It>
range_t<It> range( It b, It e ) {
return {std::move(b), std::move(e)};
}
以上是一个绝对最小的迭代器范围类型,可以for(:)
迭代。
template<class F, class R>
auto map_range( F&& f, R& r ) {
using std::begin; using std::end;
auto b = begin(r);
auto e = end(r);
using it = iterator_mapped<std::decay_t<F>, decltype(b)>;
return range( it( f, b ), it( f, e ) );
}
请注意R&
not R&&
; 在这里采取r
值是危险的。
auto GetStringIterator() const
{
return map_range( [](auto&& pair)->decltype(auto){
return pair.second;
}, m_Items );
}
并做了。
将其转换为c ++ 11是一件痛苦的事。 你必须用std::function
s来代替lambdas(或编写执行任务而不是lambda的函数对象),用auto
和尾随返回类型替换decltype(auto)
,给lambdas提供auto&&
参数的确切类型等等,你最终会得到大约25%-50%的代码,其中大部分都是模糊的类型追逐。
这基本上是boost::adaptors::map_values
作用,但是这是手动滚动的,这样你就可以理解它是如何工作的,并且没有boost依赖。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.