[英]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.