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

时间:2016-08-30 15:32:49

标签: c++ stl iterator

(另见Is there a good way not to hand-write all twelve required Container functions for a custom type in 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();
    }

还有其他事情需要考虑吗?是否有工具/技术使这个&#34;容易正确&#34;并减少锅炉板代码的数量?

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

4 个答案:

答案 0 :(得分:6)

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

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

由于您将数据存储在数组中,因此您可能也对SequenceContainer感兴趣。

答案 1 :(得分:2)

我会选择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,那么begin()和朋友的const和非const重叠应该是{{1}}访问。

答案 2 :(得分:1)

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

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和{{1}的参数,则必须使用成员函数}。

如果您希望能够使用std::cend类型的对象作为Cbeginend和{的参数,则必须使用非成员函数{1}}。 ADL将确保为此类用法找到非成员函数。

cbegin

答案 3 :(得分:-2)

为了创建有效的迭代器,必须确保std :: iterator_traits有效。这意味着您必须设置迭代器类别等。

迭代器应该实现iterator(),iterator(iterator&amp;&amp;),iterator(iterator const&amp;),operator ==,operator!=,operator ++,operator ++(int),operator *,operator =和operator-取代。添加运算符&lt;也是一个好主意。和操作员+如果可以的话(你不能总是,例如链表。)

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。 (注意:您还需要某些属性才能成为有效的&#34;容器&#34;,如.size())

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

你应该创建begin()const和end()const,但它应该是cbegin()的别名,永远不会与begin()相同!