公开自定义 STL 样式迭代的首选方法是什么?

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

(另见 Is there a good way not to hand-write all twelve required Container functions for a custom type in C++?


对于class如

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();
    }

还有其他需要考虑的事情吗?是否有 tools/techniques 来实现 "easy to get right" 并减少样板代码的数量?


一些相关的questions/discussion包括:

我选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 和非 [=15],你应该有 begin() 和朋友的 const 和非 const 重载=]访问。

有一个标准描述了您的 class 接口应该是什么样子,如果您希望它们与 STL 一致的话。 C++ 有这个 'concepts' 的概念,它确定了给定 class 的要求是一个概念的充分实现。这几乎成了c++11的一个语言特性。

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

由于您似乎将数据存储在数组中,因此您可能也对 SequenceContainer.

感兴趣

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

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::cbegin 和 [= 的参数,则成员函数是必需的16=].

如果您希望能够使用类型为 C 的对象作为仅 beginendcbegin 的参数,则非成员函数是必需的和 cend。 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 const&)、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 了解有关制作有效迭代器所需内容的更多信息。 (注意:您还需要某些属性是有效的 "container",例如 .size())

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

您应该创建 begin() const 和 end() const,但它应该是 cbegin() 的别名,绝不与 begin() 相同!