在map元素上使用for_each的方法

64
我有一个地图,我想在每个数据类型对象成员函数上执行调用。我知道如何在任何序列上执行此操作,但是,在关联容器上是否可能实现这一点?
我找到的最接近的答案是:Boost.Bind to access std::map elements in std::for_each。但是我不能在我的项目中使用boost,那么是否有STL替代品可以替换boost::bind吗?
如果不可能,我考虑创建一个临时序列指向数据对象的指针,然后在它上面调用for_each,就像这样:
class MyClass
{
public:
 void Method() const;
}

std::map<int, MyClass> Map;
//...

std::vector<MyClass*> Vector;
std::transform(Map.begin(), Map.end(), std::back_inserter(Vector), std::mem_fun_ref(&std::map<int, MyClass>::value_type::second));
std::for_each(Vector.begin(), Vector.end(), std::mem_fun(&MyClass::Method));

看起来太混乱了,我不是很喜欢它。有什么建议吗?

11个回答

153

C++11 允许你做:

for (const auto& kv : myMap) {
    std::cout << kv.first << " has value " << kv.second << std::endl;
}

C++17 允许你做:

for (const auto& [key, value] : myMap) {
    std::cout << key << " has value " << value << std::endl;
}

使用结构化绑定

更新:

如果您不想修改映射,则使用const auto更安全。


我喜欢C++17选项,但遗憾的是,如果在for循环体中使用lambda,则无法捕获它们 :( - Eugenio Miró

67
您可以遍历一个std::map对象。每个迭代器将指向一个std::pair<const T,S>,其中TS是您在map上指定的相同类型。
例如:
for (std::map<int, MyClass>::iterator it = Map.begin(); it != Map.end(); ++it)
{
  it->second.Method();
}

如果您仍然想使用std::for_each,请传递一个以std::pair<const int, MyClass>&作为参数的函数。
示例:
void CallMyMethod(std::pair<const int, MyClass>& pair) // could be a class static method as well
{
  pair.second.Method();
}

将其传递给std::for_each函数:

std::for_each(Map.begin(), Map.end(), CallMyMethod);

2
谢谢您的回答,但我想避免创建自己的循环。 - Antonio Pérez
2
@Antonio:这样做有什么特别的原因吗?任何基于模板的解决方案都应该生成最多与此相同效率的代码,或者更糟。但不太可能更快。我编辑了我的答案以添加一个更短(但等效的)解决方案。 - ereOn
我发现在STL序列中使用for_each比使用for循环更清晰(for_each(v.begin(), v.end(), &mem_fun_ref(&MyClass::Method)),所以我想知道如何在map上实现。 - Antonio Pérez
1
@Antonio:问题已经被编辑,所以你也可以尝试使用std::for_each。试一试吧 ;) - ereOn
这段代码无法编译,因为存在一个小错误。当解引用时,std::map 迭代器返回的是 std::pair<const key_type, value_type>,而不是 std::pair<key_type, value_type>。所以你需要将 CallMyMethod 的参数改为 std::pair<const int, MyClass>& - MtnViewJohn
@ereOn 不想使用for循环的一个原因是可以在断言中检查整个映射。 - danio

17

C++14引入了通用lambda函数,这意味着我们可以非常容易地使用std :: for_each:

std::map<int, int> myMap{{1, 2}, {3, 4}, {5, 6}, {7, 8}};

std::for_each(myMap.begin(), myMap.end(), [](const auto &myMapPair) {
    std::cout << "first " << myMapPair.first << " second "
              << myMapPair.second << std::endl;
});

我认为std::for_each有时比简单的基于范围的for循环更适合。例如,当您只想循环遍历映射的子集时。


明确的答案是,这种方法已经得到支持超过4年了。 - Brian Salehi

8
怎么样使用纯C++?(根据@Noah Roberts的说明修正了示例代码)
for(std::map<int, MyClass>::iterator itr = Map.begin(), itr_end = Map.end(); itr != itr_end; ++itr) {
  itr->second.Method();
}

Scott Myers,《Effective C++》。更喜欢算法而不是手写循环。一般来说,库的创建者可以根据容器的实现在代码中创建优化,这是普通人所不知道的。 - wheaties
10
过早地概括是另一种罪恶的根源。 - kennytm
3
可以说,相较于优化代码而言,可维护的代码更难让下一个程序员理解,而且除非你使用性能分析工具确定了瓶颈,否则可维护的代码价值要高得多。 - jmucchiello
1
@jmucchiello - 当然这是个人口味问题,但我不同意你的观点。事实上恰恰相反,因为算法是以循环应该执行的操作命名的,而不必通过循环中发生的步骤来解密它们。 - Edward Strange
4
尽管这些开销可能是微不足道的,但我不太喜欢for_each。它在大多数情况下都很笨拙,表达能力也不强。理想情况下,我更喜欢C++0x中新的基于范围的for循环:for (const map_pair& p : the_map) { the_vec.push_back(p.second); }for (vec_element v& : the_vec) { v.method(); }。这样的代码更易读,也更简洁。其次可以使用Boost库中的for each宏来模拟这种功能。如果不行,可以用lambda函数将算法局部化,但我认为for_each应该是最后的手段。 - GManNickG
显示剩余16条评论

3

很遗憾你没有Boost库,但如果你的STL实现有扩展,那么你可以组合mem_fun_ref和select2nd来创建一个适用于for_each的单一函数对象。代码看起来应该像这样:

#include <algorithm>
#include <map>
#include <ext/functional>   // GNU-specific extension for functor classes missing from standard STL

using namespace __gnu_cxx;  // for compose1 and select2nd

class MyClass
{
public:
    void Method() const;
};

std::map<int, MyClass> Map;

int main(void)
{
    std::for_each(Map.begin(), Map.end(), compose1(std::mem_fun_ref(&MyClass::Method), select2nd<std::map<int, MyClass>::value_type>()));
}

请注意,如果您无法访问compose1(或unary_compose模板)和select2nd,则它们很容易编写。

如果您是VS用户,并且知道如何访问STL扩展,请编辑此内容以实现跨平台兼容性。 - David Joyner
因为 libstdc++ 是基于 SGI 3.3 实现的 STL 子库来实现的,所以我们 [GNU] 也继承了它们的扩展功能。这里有一个关于它们的描述,也许有人可以将它们移植过去。https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.2/group__SGIextensions.html - Orwellophile
注意,有人在这里尝试过(未经测试),虽然它的格式不太好,也没有经过充分测试,但对我来说几乎可以编译。似乎出现了一个奇怪的错误,导致<utility>类中的std::pair出现问题。你的情况可能会有所不同。http://computer-programming-forum.com/84-vc-stl/4d17c0a678f187f0.htm - Orwellophile

2

对于从谷歌搜索到这个问题的程序员们,有一个使用boost的好方法。

在这里解释:是否可以在std::map中使用boost::foreach?

以下是一个真实示例,供您参考:

// typedef in include, given here for info : 
typedef std::map<std::string, std::string> Wt::WEnvironment::CookieMap

Wt::WEnvironment::CookieMap cookie_map = environment.cookies();

BOOST_FOREACH( const Wt::WEnvironment::CookieMap::value_type &cookie, cookie_map )
{
    std::cout << "cookie : " << cookie.first << " = " << cookie.second << endl;
}

享受。


0

这对你有用吗?

class MyClass;
typedef std::pair<int,MyClass> MyPair;
class MyClass
{
  private:
  void foo() const{};
public:
static void Method(MyPair const& p) 
{
    //......
        p.second.foo();
};
}; 
// ...
std::map<int, MyClass> Map;
//.....
std::for_each(Map.begin(), Map.end(), (&MyClass::Method));

0

只是一个例子:

template <class key, class value>
class insertIntoVec
{
public:
    insertIntoVec(std::vector<value>& vec_in):m_vec(vec_in)
    {}

    void operator () (const std::pair<key, value>& rhs)  
    {   
        m_vec.push_back(rhs.second);
    }

private:
    std::vector<value>& m_vec;
};

int main()
{
std::map<int, std::string> aMap;
aMap[1] = "test1";
aMap[2] = "test2";
aMap[3] = "test3";
aMap[4] = "test4";

std::vector<std::string> aVec;

aVec.reserve(aMap.size());
std::for_each(aMap.begin(), aMap.end(),
          insertIntoVec<int, std::string>(aVec) 
    );

}


这是次优的。复制每个 MyClass 实例并为此分配一个 std::vector 并不是最优化的做法... - ereOn
上面的示例展示了如何使用std::for_each迭代map。这里不涉及优化问题。 - aJ.
我想我们可以假设当存在优化解决方案时,OP会更喜欢它,对吧? - ereOn

0

我之前写过这个程序,可以做你现在需要的事情。

namespace STLHelpers
{
    //
    // iterator helper type for iterating through the *values* of key/value collections
    //

    /////////////////////////////////////////////
    template<typename _traits>
    struct _value_iterator
    {
        explicit _value_iterator(typename _traits::iterator_type _it)
            : it(_it)
        {
        }

        _value_iterator(const _value_iterator &_other)
            : it(_other.it)
        {
        }

        friend bool operator==(const _value_iterator &lhs, const _value_iterator &rhs)
        {
            return lhs.it == rhs.it;
        }

        friend bool operator!=(const _value_iterator &lhs, const _value_iterator &rhs)
        {
            return !(lhs == rhs);
        }

        _value_iterator &operator++()
        {
            ++it;
            return *this;
        }

        _value_iterator operator++(int)
        {
            _value_iterator t(*this);
            ++*this;
            return t;
        }

        typename _traits::value_type &operator->()
        {
            return **this;
        }

        typename _traits::value_type &operator*()
        {
            return it->second;
        }

        typename _traits::iterator_type it;
    };

    template<typename _tyMap>
    struct _map_iterator_traits
    {
        typedef typename _tyMap::iterator iterator_type;
        typedef typename _tyMap::mapped_type value_type;
    };

    template<typename _tyMap>
    struct _const_map_iterator_traits
    {
        typedef typename _tyMap::const_iterator iterator_type;
        typedef const typename _tyMap::mapped_type value_type;
    };
}

0

据我所记,C++的map可以使用map.begin()返回键的迭代器,您可以使用该迭代器循环遍历所有键,直到达到map.end(),并获取相应的值: C++ map


2
不行,迭代器总是会给你一个键值对(key,value)。 - PierreBdR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接