【问题标题】:How to implement standard iterators in class如何在类中实现标准迭代器
【发布时间】:2018-03-07 23:46:34
【问题描述】:

我的类通常使用标准容器作为基础字段。例如,我有一个班级

template <typename T>
class Vec_3D
{
public:
    /* ... */
    std::array<T, 3> vec;
    /* ... */
};

它只有一个变量vec,其余的只是我在处理向量时需要的函数。我希望能够使用基于范围的 for 循环,例如

Vec_3D<double> vec;
for (double val : vec) {/*...*/}

应该明显地迭代std::array&lt;double, 3&gt;

如何在我的类中实现迭代器,而这些迭代器又应该调用 std::array&lt;T, 3&gt; 的迭代器?

我从this question 开始,并尝试将我的类中的迭代器定义为

typedef std::iterator<std::random_access_iterator_tag, T, ptrdiff_t, T*, T&> iterator;
typedef std::iterator<std::random_access_iterator_tag, const T, ptrdiff_t, const T*, const T&> const_iterator;

inline iterator begin() noexcept { return vec.begin(); }
inline const_iterator cbegin() const noexcept { return vec.cbegin(); }
inline iterator end() noexcept { return vec.end(); }
inline const_iterator cend() const noexcept { return vec.end(); }

但出现编译错误

error: no match for ‘operator!=’ (operand types are ‘Vec_3D<double>::iterator {aka std::iterator<std::random_access_iterator_tag, double, long int, double*, double&>}’ and ‘Vec_3D<double>::iterator {aka std::iterator<std::random_access_iterator_tag, double, long int, double*, double&>}’)

operator++, operator*

【问题讨论】:

  • std::iterator 不是你想的那样

标签: c++ c++11 iterator


【解决方案1】:

std::iterator 只是一个基类,它基本上是一些特征的容器,但如果你想用它来实现你自己的迭代器类,你需要从它派生。

但是你不需要使用它,有一个建议弃用它,你可以直接在你编写的迭代器中定义这些特征。以下问题包含有关提案的信息并有助于实现迭代器类:- Preparation for std::iterator Being Deprecated

目前您正在使用该基定义容器的迭代器类型,而不是实际上可以进行任何迭代的类,这就是它失败的原因。

您将数组公开为公共成员。如果您很高兴公开您的 vec_3d 是使用数组实现的(无论您是否继续公开公开成员数组),那么您可以只使用数组的迭代器 - 从问题中不清楚您的迭代器需要任何定制行为只是因为你的容器增加了一些功能。

【讨论】:

    【解决方案2】:

    基于范围的 for 循环只要求您的类具有返回迭代器的 begin()end() 方法(或 std::begin()std::end() 的重载)。它不关心那些迭代器来自哪里。因此,最简单的解决方案是只使用数组自己的迭代器,而不是尝试定义自己的迭代器,例如:

    template <typename T>
    class Vec_3D
    {
    public:
        typedef typename std::array<T, 3> array_type;
        typedef typename array_type::iterator iterator;
        typedef typename array_type::const_iterator const_iterator;
        // or:
        // using array_type = std::array<T, 3>;
        // using iterator = array_type::iterator;
        // using const_iterator = array_type::const_iterator;
        ...
    
        inline iterator begin() noexcept { return vec.begin(); }
        inline const_iterator cbegin() const noexcept { return vec.cbegin(); }
        inline iterator end() noexcept { return vec.end(); }
        inline const_iterator cend() const noexcept { return vec.cend(); }
        ...
    
    private:
        array_type vec;
    };
    

    【讨论】:

    • 您应该尽可能使用容器自己的迭代器。除非您希望您的类仅公开容器的子集,否则无需定义自己的迭代器来委托给容器的迭代器。
    【解决方案3】:

    std::iterator 是(曾经)一个帮助器类型,用于定义典型迭代器所需的 typedefs。类中的这些 typedef 反过来使 std::iterator_traits 与您的迭代器一起工作。

    但是,它实际上并没有为您实现所需的操作。

    它已被弃用,因为 std 委员会不喜欢指定标准迭代器必须具有这些 typedef,并且编写 typedef 并不比弄清楚要传递给 std::iterator 模板的参数要麻烦多少。

    这里最简单的做法就是窃取底层容器的迭代器。这会使您的抽象泄漏,但它既高效又容易。

    template <typename T>
    struct Vec_3D {
      using container=std::array<T, 3>;
      using iterator=typename container::iterator;
      using const_iterator=typename container::const_iterator;
    
      iterator begin() { return vec.begin(); }
      iterator end() { return vec.end(); }
      const_iterator begin() const { return vec.begin(); }
      const_iterator end() const { return vec.end(); }
    private:
      /* ... */
      container vec;
      /* ... */
    };
    

    如果你不想暴露你的底层容器类型,如果你愿意保证你的底层容器是一个连续的缓冲区,你可以这样做:

    template <typename T>
    struct Vec_3D {
      using iterator=T*;
      using const_iterator=T const*;
    
      iterator begin() { return vec.data(); }
      iterator end() { return vec.data()+vec.size(); }
      const_iterator begin() const { return vec.data(); }
      const_iterator end() const { return vec.data()+vec.size(); }
    private:
      /* ... */
      std::array<T,3> vec;
      /* ... */
    };
    

    因为指针是有效的迭代器。

    如果你发现你写这个“我是一个修改过的容器”样板太多,你可以自动化它:

    template<class Container>
    struct container_wrapper {
      using container=Container;
    
      using iterator=typename container::iterator;
      using const_iterator=typename container::const_iterator;
    
      iterator begin() { return m_data.begin(); }
      iterator end() { return m_data.end(); }
      const_iterator begin() const { return m_data.begin(); }
      const_iterator end() const { return m_data.end(); }
    protected:
      Container m_data;
    };
    

    然后

    template <typename T>
    class Vec_3D:private container_wrapper<std::array<T,3>> {
      // ...
    };
    

    但即使这样也可能有点多,为什么不只是:

    template <typename T>
    class Vec_3D:public std::array<T,3> {
      // ...
    };
    

    确实,通过指向base的指针删除Vec_3D是未定义的行为,但是谁删除指向标准容器的指针呢?

    如果这让您担心:

    template <typename T>
    class Vec_3D: private std::array<T,3> {
      using container = std::array<T,3>;
      using container::begin();
      using container::end();
      // ...
    };
    

    允许您私下继承,然后将某些操作带回范围。

    【讨论】:

    • @bob 是的,我有。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-23
    • 2016-03-30
    相关资源
    最近更新 更多