如何在自定义 class (w/o boost) 中隐藏迭代器和容器实现

How to hide Iterator and container implemtation in a custom class (w/o boost)

最近一天左右,我一直在为这件事绞尽脑汁。我试图找出如何从 class return 迭代器,同时隐藏 class 正在使用的容器类型。一个例子是我有一个 class canvas,它包含具有相同界面的小部件,并且它们被私密地存储在 std::vector 中。所以....

简化代码

class Canvas  
{  
public:  
    WidgetIterator begin();  
    WidgetIterator end();  
private:
    class Impl;
    std::unique_ptr<Impl> mImpl;  
};

class Canvas::Impl  
{
public:  
    std::vector<widget> mWidget;  
    //how to return mWidget.begin(), mWidget.end() up as widget iterator??  
}  

usage:  
int main()  
{  
    Canvas canvas();
    ...Fill canvas with widgets...  
    //iterate over widgets  
    for(auto& a_widget : canvas)  
    {  
        //do something with / to a_widget.  User does not know or care  
        //that underlying iterator is std::vector<widget>::iterator 
    }  
    ...Do more stuff....  
    return 0;  
}  

基本上,我想通过 Canvas::begin() 和 Canvas::end() 以某种方式为 mWidget.begin() 和 mWidget.end() 添加别名。用户知道迭代器是一个小部件,他们只是不需要知道迭代器是 std::vector::iterator。我正在尝试使用 PIMPL 隐藏我的实现并保留有关内容如何存储在 class.

中的信息

我好像找不到合适的"formula"。我查看了 type-erasure 并尝试通过接口 return 函数指针,但我似乎想不出一种方法将 std::vector::iterator 排除在 header,到目前为止,我所看到的一切似乎都不符合我想要做的事情。有人能指出我正确的方向吗?阅读material?我缺少一个概念吗?哦 - 我已经看到一些使用 boost int 的例子,我无法弄清楚如何在我的情况下工作。我想避免这种情况,因为我正在尝试减少外部依赖性。

键入擦除迭代器的最简单方法是编写输入迭代器生成器。

这足以 for(:) 循环,但不是所有其他算法,并且可以轻松包装任何随机访问容器:

template<class T>
struct gen_iterator_t {
  std::function<T(std::size_t)> f;
  std::size_t n = 0;
  gen_iterator& operator++() { ++n; return *this; }
  gen_iterator operator++(int) { auto r = *this; ++*this; return r; }
  T operator*() { return f(n); }
  gen_iterator_t( gen_iterator_t const& )=default;
  gen_iterator_t( gen_iterator_t && )=default;
  gen_iterator_t& operator=( gen_iterator_t const& )=default;
  gen_iterator_t& operator=( gen_iterator_t && )=default;
  gen_iterator_t()=default;

  explicit operator bool() const { return static_cast<bool>(f); }

  gen_iterator_t( std::function<T(std::size_t)> fin, std::size_t i ):
    f(fin), n(i)
  {}

  friend bool operator==( gen_iterator_t const& lhs, gen_iterator_t const& rhs ) {
    if (!lhs && !rhs) return true;
    if (!lhs || !rhs) return false;
    return lhs.n == rhs.n;
  }
  friend bool operator==( gen_iterator_t const& lhs, gen_iterator_t const& rhs ) {
    return !(lhs==rhs);
  }
};

然后我们写range:

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 s, It f ) { return {s,f}; }

并生成范围:

template<class F>
range_t< gen_iterator_t< std::result_of_t<F&(std::size_t)> > >
generate_range( F f, std::size_t start, std::size_t finish ) {
  return { {f, start}, {f, finish} };
}

现在你的class暴露了

class Canvas {
public:
  range_t< gen_iterator_t< widget& > > get_widgets();
};

实现为

range_t< gen_iterator_t< widget& > > Canvas::get_widgets() {
  return generate_range(
    [this]( std::size_t n )->widget& { return mImpl->mWidget[n]; },
    0, mImpl->mWidget.size()
  );
}

很少暴露。

如果您想更进一步,让它能够包装非随机访问容器,那就需要多做一些工作了。