公开自定义stl风格迭代的首选方式是什么?

what is the preferred way to expose custom STL-style iteration?

本文关键字:方式 是什么 迭代 自定义 stl 风格      更新时间:2023-10-16

(另请参阅是否有一种好方法可以避免在c++中为自定义类型手写所有十二个必需的容器函数?)


对于像

这样的类
namespace JDanielSmith {
class C
{
    const size_t _size;
    const std::unique_ptr<int[]> _data;
public:
    C(size_t size) : _size(size), _data(new int[size]) {}
    inline const int* get() const noexcept { return _data.get(); }
    inline int* get() noexcept { return _data.get(); }
    size_t size() const noexcept { return _size; }
};
}

公开迭代的首选方式是什么?我应该写begin()/end()(和cbegin()/cend())成员函数吗?

const int* cbegin() const {
    return get();
}
const int* cend() const {
    return cbegin() + size();
}

还是应该是非成员函数?

const int* cbegin(const C& c) {
    return c.get();
}
const int* cend(const C& c) {
    return cbegin(c) + c.size();
}

begin()/end()应该同时具有const和非const过载吗?

    const int* begin() const {
        return get();
    }
    int* begin() {
        return get();
    }

还有什么要考虑的吗?是否有工具/技术使这"容易得到正确"并减少样板代码的数量?


一些相关的问题/讨论包括:

    自定义容器应该有自由的开始/结束功能吗?为什么在c++ 11中使用非成员的开始和结束函数?
  • 何时使用std::beginstd::end而不是容器特定版本

如果你想让你的类接口与STL一致,有一个标准描述了它们应该是什么样子。c++有"概念"的概念,它确定了给定类要充分实现一个概念的要求。这几乎成为c++11的一个语言特性。

您可能感兴趣的一个概念是容器概念。如您所见,为了满足容器概念的需求,您需要begincbeginendcend作为成员函数(以及其他功能)。

因为它看起来像是在数组中存储数据,所以您可能还对SequenceContainer感兴趣。

我选c。

这里的主要问题是std::begin()实际上不能用ADL找到非成员begin()。因此,真正的解决方案是编写自己的

namespace details {
    using std::begin;
    template <class C>
    constexpr auto adl_begin(C& c) noexcept(noexcept(begin(c)))
        -> decltype(begin(c))
    {
        return begin(c);
    }
}
using details::adl_begin;

现在,不管你是把begin()写为成员函数还是非成员函数,只要在任何地方使用adl_begin(x),它就会工作。对于标准容器和原始数组也是如此。这很方便地回避了成员与非成员的讨论。


是的,如果你想公开const和非const访问,你应该有const和非constbegin()和朋友重载。

我建议创建两组函数——成员函数和非成员函数——以获得最大的灵活性。

namespace JDanielSmith {
   class C
   {
      const size_t _size;
      const std::unique_ptr<int[]> _data;
      public:
      C(size_t size) : _size(size), _data(new int[size]) {}
      inline const int* get() const { return _data.get(); }
      inline int* get() { return _data.get(); }
      size_t size() const { return _size; }
      int* begin() { return get(); }
      int* end() { return get() + _size; }
      const int* begin() const { return get(); }
      const int* end() const { return get() + _size; }
      const int* cbegin() const { return get(); }
      const int* cend() const { return get() + _size; }
   };
   int* begin(C& c) { return c.begin(); }
   int* end(C& c) { return c.end(); }
   const int* begin(C const& c) { return c.begin(); }
   const int* end(C const& c) { return c.end(); }
   const int* cbegin(C const& c) { return c.begin(); }
   const int* cend(C const& c) { return c.end(); }
}

如果您希望能够使用C类型的对象作为std::beginstd::endstd::cbeginstd::cend的参数,则成员函数是必需的。

如果您希望能够使用C类型的对象作为begin, end, cbegincend的参数,则非成员函数是必要的。ADL将确保在这些用法中找到非成员函数。

int main()
{
   JDanielSmith::C c1(10);
   {
      // Non-const member functions are found
      auto b = std::begin(c1);
      auto e = std::end(c1);
      for (int i = 0; b != e; ++b, ++i )
      {
         *b = i*10;
      }
   }
   JDanielSmith::C const& c2 = c1;
   {
      // Const member functions are found
      auto b = std::begin(c2);
      auto e = std::end(c2);
      for ( ; b != e; ++b )
      {
         std::cout << *b << std::endl;
      }
   }
   {
      // Non-member functions with const-objects as argument are found
      auto b = begin(c2);
      auto e = end(c2);
      for ( ; b != e; ++b )
      {
         std::cout << *b << std::endl;
      }
   }
}

为了创建一个有效的迭代器,必须确保std::iterator_traits是有效的。这意味着你必须在其他事物中设置迭代器类别。

迭代器应该实现iterator()、iterator(iterator&&)、iterator(iterator constator &)、operator==、operator !=、operator++、operator++(int)、operator*、operator=和operator->。加上operator<如果可以,使用operator+(不能总是这样,例如链表)>

template <typename T>
class foo
{
public:
  using value_type = T;
  class iterator 
  { 
  public:
    using value_type = foo::value_type;
    using iterator_category = std::random_access_iterator_tag;
    // or whatever type of iterator you have...
    using pointer = value_type*;
    using reference = value_type&;
    using difference_type = std::ptrdiff_t;
    // ... 
  };
  class const_iterator 
  {
    // ... 
  };
  iterator begin() { /*...*/ }
  iterator end() { /*...*/ }
  const_iterator cbegin() const { /*...*/ }
  const_iterator cend() const { /*...*/ }
  /* ... */
};

参见:http://en.cppreference.com/w/cpp/iterator/iterator_traits了解创建有效迭代器所需的更多信息。(注意:您还需要某些属性成为有效的"容器",如.size())

理想情况下,你应该使用成员函数开始和结束,但这不是必需的…还可以重载std::begin和std::end。如果你不知道怎么做,我建议你使用成员函数。

你应该创建begin() const和end() const,但它应该是cbegin()的别名,绝不能和begin()一样!