【问题标题】:Iterator for C++11 map values (simple and transparent)C++11 映射值的迭代器(简单透明)
【发布时间】:2014-06-29 15:13:29
【问题描述】:

我正在寻找一种简单的方法来为 C++11 中的 map 的值创建迭代器。

这个方法应该简单和透明:简单是因为它应该易于实现,透明是因为客户端不应该知道值来自映射,而不是集合。

这个问题已经被问过好几次了。其中许多问题早于 C++11 并使用了我不想使用的 boost。有些并不简单,John Ahlgren 的解决方案在这里,http://john-ahlgren.blogspot.com/2013/10/how-to-iterate-over-values-of-stdmap.html,例如需要一页代码来编写自定义迭代器。

其他的不是透明的,也就是说,很明显可以写:

map<string,foo> mymap;
for (auto it=mymap.begin();it!=mymap.end();++it){
  Foo val= it->second;
  ...
 }

但是,我不想这样做,因为我不希望客户端知道数据表示。

问题如下。

我有一堆用长“键”唯一索引的对象。有时我想操纵这些对象的集合。其他时候我想检索一个给定键的对象。

我不能直接使用“set”类有几个原因,其中主要是它不存储可变实例,并且这些实例必须是可变的(显然,除了键)。

所以,我决定将我所有的对象存储在一个巨大的全局哈希表中:

map<long,Foo> all_the_objects;

然后我根本不使用set&lt;Foo&gt;。相反,我使用 set&lt;long&gt; 并使用适配器来模拟一组 Foo,即,

class SetOfFoo{
  private: set<long> theKeys;
  public:
    void insert(const & Foo);
    size_t size() return theKeys.size();
    bool is_member(const & Foo)
      {return theKeys.find(Foo.key)
          != theKeys.end;}
    Foo & insert(const & Foo val){
       long key=val.key;
       all_the_objects[key]=val;
       return all_the_objects[key];
     }
    ...::iterator begin() {???}
}

换句话说,SetOfFoo 类的客户端不知道或不需要知道 SetOfFoo 是作为键集实现的。

我也不能自己在适配器类中创建一个 Vector,因为不能将引用存储在 C++ 集合中。

真的不可能用一种简单、透明的方式来迭代 map 值吗?我很难相信,因为这是一个非常普遍的需求,而且在我见过的每一种具有哈希表的语言中都是微不足道的。我只是不明白这有多难。

【问题讨论】:

  • 如果我这样做,我会使用for(auto const&amp; value: mymap | boost::adaptors::map_values) {/*...*/}。这使用来自Boost.Rangeboost::adaptors::map_values
  • 旁注:std::map 使用树表示,不是哈希表。如果您确实需要 O(1) 查找,请使用 std::unordered_map
  • 我不想使用 Boost。我认为 STL 取代了 Boost,无论如何,我在这段代码中已经有多个巨大的东西,我在编译和链接它时遇到了足够多的麻烦。我不会为这样的小事添加一个全新的库!
  • 因此您需要一个简单、易用、通用的解决方案。有人写了一个简单易用的通用解决方案,但你不想使用它......
  • 这根本不是地图的基本功能,因为它可以在地图之上实现而不会影响性能。提供关于在哈希表中放置元素的提示的能力是基本的,因为类的外部用户在不修改类本身的内部细节的情况下无法实现这一点。

标签: c++ c++11 map iterator std


【解决方案1】:

这很简单。

这是一个极其简单的版本,它最低限度地解决了整数到字符串的映射问题。您可以重写为您想要的类型,也可以根据需要对其进行模板化。

#include <map>
#include <iostream>
#include <iterator>
#include <string>
#include <algorithm>

struct map_value_iterator : public std::map<int, std::string>::const_iterator
{
    map_value_iterator(std::map<int, std::string>::const_iterator src)
    : std::map<int, std::string>::const_iterator(std::move(src))
    {

    }

    // override the indirection operator
    const std::string& operator*() const {
        return std::map<int, std::string>::const_iterator::operator*().second;
    }
};


using namespace std;


int main()
{
    map<int, string> myMap { {1, "Hello" }, { 2, "World" } };

    copy(map_value_iterator(begin(myMap)), map_value_iterator(end(myMap)), ostream_iterator<string>(cout , " "));
    cout << endl;

    return 0;
}

程序输出:

Compiling the source code....
$g++ -std=c++11 main.cpp -o demo -lm -pthread -lgmpxx -lgmp -lreadline 2>&1

Executing the program....
$demo 
Hello World 

【讨论】:

  • 这是一个非常好的想法,比我见过的任何其他解决方案(包括 Ahlgren 解决方案)都更简单、更容易。所以不知何故++和==,我认为for循环甚至可能需要复制,“只是工作”?这就提出了一个相关的问题:我们什么时候可以保证我们可以覆盖 STD 类中的操作并且仍然让基类“正常工作”? (就此而言,我们什么时候可以只继承一个 STD 类,并保证这一点)。我们怎么知道覆盖 * 之类的东西不会破坏某些东西?特别是我猜它不是虚拟的......
  • 为了清楚起见,Ahlgren 为迭代器使用适配器并分派所有内容,而不是子类。这需要大约 15 倍于您的解决方案的代码,并且更难阅读 - 但他认为有必要这样做吗?
  • 在某些极端情况下,我的超简单解决方案需要添加一些内容才能使迭代器成为 100% 正确符合的迭代器,但是原则上只要子类化没有问题你很清楚什么有效,什么无效。例如,基迭代器类没有虚拟析构函数,它的value_type 将是std::pair&lt;int, std::string&gt;,所以实际上它也应该被覆盖为std::string。另一种方法是通过封装,但这是更多样板代码。
【解决方案2】:

您可以执行以下操作 (C++98):

#include <iostream>
#include <map>
#include <string>
#include <algorithm>

#include "util/pair_iterator.hpp"

template<class T> inline T const& constify(T& t) { return t; }

int main()
{
    using namespace std;
    using namespace util;

    map<int, string> m;
    m[0] = "alice";
    m[1] = "bob";
    m[2] = "carol";
    m[3] = "dave";
    m[4] = "eve";

    copy(
          over_second(m.begin())
        , over_second(m.end())
        , ostream_iterator<string>(cout, "\n")
        );

    copy(
          over_first(m.begin())
        , over_first(m.end())
        , ostream_iterator<int>(cout, "\n")
        );

    // const iterators check

    copy(
          over_second(constify(m).begin())
        , over_second(constify(m).end())
        , ostream_iterator<string>(cout, "\n")
        );

    copy(
          over_first(constify(m).begin())
        , over_first(constify(m).end())
        , ostream_iterator<int>(cout, "\n")
        );
}

这是一个实现:

// util/pair_iterator.hpp
#include <iterator>

#include "boost/iterator/transform_iterator.hpp"
#include "boost/type_traits/remove_reference.hpp"
#include "boost/type_traits/is_const.hpp"
#include "boost/mpl/if.hpp"

namespace util {

namespace aux {

template<class T> struct dereference_type
    : boost::remove_reference<typename std::iterator_traits<T>::reference>
{
};

template<class PairT>
struct first_extracter
{
    typedef typename boost::mpl::if_<
          boost::is_const<PairT>
        , typename PairT::first_type const
        , typename PairT::first_type
        >::type result_type;
    result_type& operator()(PairT& p) const { return p.first; }
};

template<class PairT>
struct second_extracter
{
    typedef typename boost::mpl::if_<
          boost::is_const<PairT>
        , typename PairT::second_type const
        , typename PairT::second_type
        >::type result_type;
    result_type& operator()(PairT& p) const { return p.second; }
};

} // namespace aux {

template<class IteratorT>
inline
boost::transform_iterator<aux::first_extracter<typename aux::dereference_type<IteratorT>::type>, IteratorT>
over_first(IteratorT const& i)
{
    typedef aux::first_extracter<typename aux::dereference_type<IteratorT>::type> extracter;
    return boost::transform_iterator<extracter, IteratorT>(i, extracter());
}

template<class IteratorT>
inline
boost::transform_iterator<aux::second_extracter<typename aux::dereference_type<IteratorT>::type>, IteratorT>
over_second(IteratorT const& i)
{
    typedef aux::second_extracter<typename aux::dereference_type<IteratorT>::type> extracter;
    return boost::transform_iterator<extracter, IteratorT>(i, extracter());
}

} // namespace util

【讨论】:

  • 这太复杂了:它是透明的,但并不简单。我是 C++ 新手,我想这只是一种文化,一页有些不透明的代码,四个包含文件和一个第三方库,可以做任何其他带有哈希表的语言都可以在一行代码中做的事情.
  • @kdog:嗯,有两个仿函数和两个工厂函数,其余的由 boost 变换迭代器处理。用法很简单:只包含一个头文件。
  • 哇。我想我将只使用指向 Foo 的指针向量并称之为一天。虽然,一个指向哈希表中的值的指针在哈希表的整个生命周期内都是有效的,这很明显吗?我知道引用是正确的,我只是问过,不确定是否也适用于指针。
  • @kdog 是的,只是不要向老板展示你的代码,你会没事的)
猜你喜欢
  • 2021-04-15
  • 2011-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-19
相关资源
最近更新 更多