【问题标题】:what is the preferred way to expose custom STL-style iteration?公开自定义 STL 样式迭代的首选方法是什么?
【发布时间】:2017-01-06 23:38:14
【问题描述】:

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

还有其他需要考虑的事情吗?是否有工具/技术可以使这种“易于正确处理”并减少样板代码的数量?


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

【问题讨论】:

  • 两者都应该有;成员以及免费(或在添加 free 版本之前考虑是否适合您的情况使用 std::beginstd::end 对)。此外,您还应该有 begin()end() 对。还有,成员类型,iteratorconst_iterator 而不是 const int* 左右。
  • @SteveJessop:不。我根本没有意思。我说的是一般意义上的:如果std::begin 不适合你,那么你应该在同一个命名空间中添加你的,这样 ADL 才能工作。
  • @Dan:对于您发布的代码,您不必编写免费版本,因为std::beginfamily 可以正常工作。

标签: c++ stl iterator


【解决方案1】:

我会选择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 重载。

【讨论】:

  • @Dan 你宁愿记得用using std::begin; using std::end; 乱扔你的代码吗?
  • @MikeMB 因为这不适用于具有非成员 begin() 的类型。
  • @TemplateRex 将adl_begin() 列为不如begin() 简洁是很奇怪的,因为能够写begin() 还需要写using std::begin;,这是净更多字符?
  • @Barry: "因为这不适用于具有非成员 begin() 的类型。" 但是你在 写作容器。如果您愿意,您的容器可以拥有成员 begin。我可以理解为您无法控制的容器添加非成员专业化到 std::begin/end。但如果它是你的容器,我认为走非标准的使用成员函数的路线不会有任何收获。
【解决方案2】:

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

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

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

【讨论】:

  • Container 里面有一些这个类目前没有的东西,大的正在复制。替代方案是:满足 Boost.Range 的要求,这是“您可以迭代并因此使用算法的东西”;或者只做足够的基于范围的 for 循环工作。
  • 好吧。原始数组不是容器。它们与&lt;algorithm&gt; 中的所有内容“一致”。
  • @Dan 另一种方法是使C 成为一个允许您指定数据存储方式的模板。这样,您可以将类接口保留为更传统的成员函数(也许这只是我的观点)。如果您需要一个不是成员的begin,则可以使用std::beginC,假设C::begin 是合理的。
  • @Dan 好吧,我想回到我关于 Container 概念的原始答案以及 Steve Jessop 对 Boost.Range 概念的评论(这是你所问的更简单和直接的问题)。这是常规迭代所需要的。
  • 请注意,他们必须更改 Container 的定义/先决条件以将新容器包含为 std::array...
【解决方案3】:

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

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 类型的对象用作beginendcbegincend 的参数,则非成员函数是必需的。 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;
      }
   }
}

【讨论】:

  • 我错过了什么吗?为什么每个人都在实现自己的非成员 beginend 函数?为什么std::beginstd::end 不够用?
  • @Dan,这是很多样板代码。与收益相比,创建此类样板代码的成本非常小。
  • "如果您希望能够将 C 类型的对象用作开始、结束、cbegin 和 cend 的参数,则非成员函数是必需的" 不,您不需要,默认 @ 987654338@ 已经调用了会员版本。如果您确实无法添加成员版本,例如使用数组或第 3 方,则只需要编写非成员版本。
  • RSahu:这是通常推荐的做事方式,我认为没有理由删除它。 @Guvante:我知道全局开始的好处,我只是看不到 custom 全局开始的好处。默认看起来效果很好。
【解决方案4】:

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

迭代器应该实现 iterator()、iterator(iterator&&)、iterator(iterator const&)、operator==、operator !=、operator++、operator++(int)、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())

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-30
    • 2012-06-19
    • 1970-01-01
    • 2012-03-15
    • 1970-01-01
    • 2015-10-01
    • 1970-01-01
    • 2010-12-15
    相关资源
    最近更新 更多