专用地图值的基于范围的 for 循环
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;
}
}
为了允许任何MyObject
客户端(在本例中为main()
函数(在不复制任何数据的情况下迭代m_Items
映射的值,应该RETURNTYPE
和IMPLEMENTATION
是什么?似乎这应该可以通过基于 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
的非常量版本来禁用映射值的修改:
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
}
请注意,如果 map 是实现细节,则应使用 @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;
}
或其范围 v3 等效物,view::values
.两者都可以像values(m)
而不是m | values
一样使用,如果你喜欢的话。
这两种解决方案都会返回地图值的视图。这是一个不拥有任何底层元素的对象,复制成本很低 - 即 O(1(。我们不是在腼腆地绘制地图或其任何潜在元素。
您可以像使用任何其他范围一样使用它:
for (std::string const& s : o.GetStringIterator()) {
// ...
}
此循环不复制任何字符串。每个s
都直接引用map
存储的相应string
。
我将首先在 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
s 来代替 lambda(或者编写执行任务的函数对象而不是 lambda(,用auto
和尾随返回类型替换decltype(auto)
,为 lambda 提供确切类型的auto&&
参数等。 你最终会得到大约 25%-50% 的代码,其中大部分是晦涩的类型追逐。
这基本上是boost::adaptors::map_values
所做的,但这是手工滚动的,因此您可以了解它的工作原理并且没有 boost 依赖项。
- 在基于范围的for循环中使用结构化绑定声明
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- 基于范围的 for 循环:迭代使用一个元素扩展的向量
- 基于范围的 for 循环unordered_map和引用
- C++基于范围的 for 循环和元素副本
- 实现基于链表的堆栈的基于范围的 for 循环
- 在基于范围的 for 循环期间插入 std::list 的后面
- 基于范围的 for 循环range_declaration中各种说明符之间的性能差异
- 避免在基于反向范围的for循环实现中悬挂参考
- C++ - 使用基于范围的 for 循环将字符值分配给向量中的字符串不会分配值
- 转到基于范围的 for 循环中的下一个迭代器
- 使用基于数组和范围的 For 循环替换一些基本代码行
- C++:返回一个基于范围 for 循环迭代器,其中包含继承对象
- 布尔值向量的基于范围 for 循环
- 在 c++ 中基于范围的 for 循环中使用引用作为控制变量
- C++11 基于范围的 for 循环,用于 std::list
- 堆分配数组的基于范围的 for 循环
- 关于在向量向量上使用基于范围的 for 循环
- 如何使用基于范围的for循环迭代Rapidjson文档(它本身就是一个JSON数组)
- 使用基于范围的for循环取消对矢量指针的引用