我有以下代码:
#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()
函数)迭代{ {1}}地图,而不复制任何数据?似乎这对于基于c ++ 11 range的循环和迭代器应该是可能的。但是我还不知道怎么做。
答案 0 :(得分:8)
基于范围的迭代可以像这样实现:
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
的非常量版本来禁用对映射值的修改:
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;
};
然后,尝试在range-for循环中修改该值将导致编译错误:
MyObject a;
for(auto & [key,value] : a) {
//value.push_back('a'); // Not OK
}
for(auto & [key,value] : a) {
cout << value; // OK
}
请注意,如果地图是实现的详细信息,则应使用@Barry提出的答案,因为它仅在地图的值上进行迭代,而不在键上进行迭代。
答案 1 :(得分:4)
您可以使用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
一样使用。
这两种解决方案都会在地图的值上返回一个 view 。这是一个不拥有任何底层元素的对象,并且复制便宜(即O(1))。我们不会改变地图或其任何底层元素。
您可以将其用作其他范围:
for (std::string const& s : o.GetStringIterator()) {
// ...
}
此循环不复制任何字符串。每个s
直接指向string
存储的相应map
。
答案 2 :(得分:3)
我将首先在c++14中回答这个问题。
这是最小的映射迭代器:
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&
不是R&&
;在这里为r
取右值是危险的。
auto GetStringIterator() const
{
return map_range( [](auto&& pair)->decltype(auto){
return pair.second;
}, m_Items );
}
完成。
将其转换为c++11很痛苦。您必须扔掉std::function
来代替lambda(或编写执行任务而不是lambda的函数对象),将decltype(auto)
替换为auto
并尾随返回类型,给出确切的值auto&&
到lambdas等参数的类型,等等。最终,您将获得大约25%-50%的代码,其中大部分使类型的追赶变得模糊。
这基本上是boost::adaptors::map_values
的工作,但是它是手动滚动的,因此您可以了解它的工作原理,并且没有增强依赖。