【问题标题】:Returning different iterators with virtual derived methods使用虚拟派生方法返回不同的迭代器
【发布时间】:2016-06-22 07:40:17
【问题描述】:

假设我有一个虚拟基类Base,它的部分行为类似于一个容器,具有两个派生类VectorLikeRangeLike。 我想实现以下目标:

class VectorLike : public Base {
    std::vector<int> data;
public:
    virtual std::vector<int>::const_iterator cbegin() { return data.cbegin() }
    virtual std::vector<int>::const_iterator cend() { return data.cend() }
}

class RangeLike : public Base {
    int min, max;
    class const_iterator {
        int x;
    public:
        int operator++() { return ++x }
        bool operator==( const_iterator rhs ) { return x == rhs.x }
        const_iterator( int y ) { x = y }
    }
public:
    virtual const_iterator cbegin() { return const_iterator( min ); }
    virtual const_iterator cend() { return const_iterator( max ); }
}

此代码将无法编译,因为 std::vector&lt;int&gt;::const_iteratorRangeLike::const_iterator 不相同或 covariant。 要实现第二个,我需要一个迭代器基类,std::vector&lt;int&gt;::const_iteratorRangeLike::const_iterator 都将从该基类派生。但是cbegin()cend() 仍然必须返回指向迭代器的指针,这将造成更大的混乱。

我的问题是,是否可以实现类似上述代码的功能,如果可以,如何实现?

【问题讨论】:

标签: c++ oop iterator


【解决方案1】:

这是一个多态 const int 迭代器的实现。您可以使用 std::iterator_traits&lt;Iter&gt;::value_type 解析为 int 的任何迭代器类型(包括指针)构造它。

std::vector&lt;int&gt;your_range_type&lt;int&gt; 都应该是这种情况。

这应该让你开始。

#include <iostream>
#include <vector>
#include <array>
#include <memory>
#include <algorithm>

struct poly_const_iterator
{
    using value_type = int;

    struct concept {
        virtual void next(int n) = 0;
        virtual const value_type& deref() const = 0;
        virtual bool equal(const void* other) const = 0;
        virtual std::unique_ptr<concept> clone() const = 0;
        virtual const std::type_info& type() const = 0;
        virtual const void* address() const = 0;
        virtual ~concept() = default;
    };

    template<class Iter>
    struct model : concept
    {
        model(Iter iter) : _iter(iter) {}

        void next(int n) override { _iter = std::next(_iter, n); }
        const value_type& deref() const override { return *_iter; }
        bool equal(const void* rp) const override { return _iter == static_cast<const model*>(rp)->_iter; }
        std::unique_ptr<concept> clone() const override { return std::make_unique<model>(*this); }
        const std::type_info& type() const override { return typeid(_iter); }
        const void* address() const override { return this; }


        Iter _iter;
    };

    std::unique_ptr<concept> _impl;

public:
    // interface

    // todo: constrain Iter to be something that iterates value_type
    template<class Iter>
    poly_const_iterator(Iter iter) : _impl(std::make_unique<model<Iter>>(iter)) {};

    poly_const_iterator(const poly_const_iterator& r) : _impl(r._impl->clone()) {};

    const value_type& operator*() const {
        return _impl->deref();
    }

    poly_const_iterator& operator++() {
        _impl->next(1);
        return *this;
    }

    bool operator==(const poly_const_iterator& r) const {
        return _impl->type() == r._impl->type()
        and _impl->equal(r._impl->address());
    }

    bool operator != (const poly_const_iterator& r) const {
        return not(*this == r);
    }
};

void emit(poly_const_iterator from, poly_const_iterator to)
{
    std::copy(from, to, std::ostream_iterator<int>(std::cout, ", "));
    std::cout << std::endl;
}

int main()
{
    std::vector<int> v = { 1, 2, 3, 4, 5 };
    std::array<int, 5> a = { 6, 7,8, 9, 0 };

    emit(std::begin(v), std::end(v));
    emit(std::begin(a), std::end(a));


    return 0;
}

预期结果:

1, 2, 3, 4, 5,
6, 7, 8, 9, 0,

【讨论】:

  • 太完美了!您通过模板类强制继承,然后巧妙地将迭代器类包装在指针周围。在 emit 中的使用看起来也很干净。
  • 不要忘记添加移动构造函数、赋值和移动赋值运算符。对不起,我忘记了。
  • @RichardHodges 您将如何实现越过结束并将迭代器设置为状态,以便它与对应于默认构造的迭代器的“结束”迭代器进行比较?
  • 我认为这不可能
  • @RichardHodges 我有一个疯狂的设置,我将 std::function 从 poly_const_iterator 传递给模型,当到达终点时要调用它。我假设迭代器知道那是什么时候,因为它知道容器中有多少元素。我传递的 lambda 是 [this]{ _impl.reset(); },因此模型会从自身内部删除,类似于调用 delete this;,但好处是将指针设置为 null,这反过来又使其评估等于默认构造的迭代器。但我希望你有一些不那么疯狂的事情:)
【解决方案2】:

这是一个基于 C++20 的解决方案(部分实现)...只是想我会把它放在那里。

它使用带有堆栈存储而不是多态和动态分配的变体,因此它可能具有更好的性能。

它应该适用于具有公共value_type 并且其reference 类型为value_type&amp; 的任何给定迭代器类型集。它也应该相当容易适应其他类型的迭代器并包含operator-&gt;

不声称这比原始答案中的实现更好。只是一个有趣的选择......

PS。以下名称中的normal 仅表示基于基本指针的迭代器。

  template <typename _Iterator, typename _Value, typename _Reference, typename _Difference>
  concept forward_iterator_for
    = std::forward_iterator<_Iterator>
      && std::same_as<std::iter_value_t<_Iterator>, _Value>
      && std::same_as<std::iter_reference_t<_Iterator>, _Reference>
      && std::same_as<std::iter_difference_t<_Iterator>, _Difference>;

  template <typename _Iterator, typename _Value>
  concept normal_forward_iterator_for //
    = std::same_as<std::remove_cvref_t<_Value>, std::remove_const_t<_Value>>
      && forward_iterator_for<_Iterator, std::remove_const_t<_Value>, _Value&, std::ptrdiff_t>;

  template <typename _Value, normal_forward_iterator_for<_Value> ... _Iterator>
  class normal_forward_iterator_variant {
   public:
    using iterator_category = std::forward_iterator_tag;
    using difference_type   = std::ptrdiff_t;
    using value_type        = std::remove_cv_t<_Value>;
    using reference         = _Value&;
    using iterator_type     = std::variant<_Iterator...>;

   private:
    iterator_type _m_iter;

   public:
    normal_forward_iterator_variant() = default;

    template <normal_forward_iterator_for<_Value> _Iter, typename... _Args>
    normal_forward_iterator_variant(std::in_place_type_t<_Iter>, _Args&&... args)
    noexcept (std::is_nothrow_constructible_v<_Iter, _Args&&...>)
    : _m_iter(std::in_place_type<_Iter>, std::forward<_Args>(args)...) {}

    normal_forward_iterator_variant(iterator_type const& iter)
    noexcept (std::is_nothrow_copy_constructible_v<iterator_type>)
    : _m_iter(iter) {}

    normal_forward_iterator_variant(normal_forward_iterator_variant const&) = default;

    normal_forward_iterator_variant(normal_forward_iterator_variant&&) = default;

    constexpr normal_forward_iterator_variant&
    operator=(normal_forward_iterator_variant const& iter)
    noexcept (std::is_nothrow_copy_assignable_v<iterator_type>) //
    requires std::is_copy_assignable_v<iterator_type> {
      _m_iter = iter._m_iter;
      return *this;
    }

    constexpr normal_forward_iterator_variant&
    operator=(normal_forward_iterator_variant&& iter)
    noexcept (std::is_nothrow_move_assignable_v<iterator_type>) //
    requires std::is_move_assignable_v<iterator_type> {
      _m_iter = std::move(iter._m_iter);
      return *this;
    }

    template <typename _Tp>
    constexpr normal_forward_iterator_variant&
    operator=(_Tp const& x)
    noexcept (std::is_nothrow_assignable_v<iterator_type, _Tp&>) //
    requires std::is_assignable_v<iterator_type, _Tp&> {
      _m_iter = x;
      return *this;
    }

    template <typename _Tp>
    constexpr normal_forward_iterator_variant&
    operator=(_Tp&& x)
    noexcept (std::is_nothrow_assignable_v<iterator_type, _Tp&&>) //
    requires std::is_assignable_v<iterator_type, _Tp&&> {
      _m_iter = std::move(x);
      return *this;
    }

    [[nodiscard]] constexpr reference
    operator*() const noexcept {
      return std::visit([](auto&& iter) -> reference {
        return *iter;
      }, _m_iter);
    }

    constexpr normal_forward_iterator_variant&
    operator++() noexcept {
      std::visit([](auto&& iter) {
        ++iter;
      }, _m_iter);
      return *this;
    }

    constexpr normal_forward_iterator_variant
    operator++(int) noexcept {
      normal_forward_iterator_variant rv(*this);
      ++*this;
      return rv;
    }

    [[nodiscard]] friend constexpr bool
    operator==(normal_forward_iterator_variant const& a, normal_forward_iterator_variant const& b) noexcept {
      return (a._m_iter == b._m_iter);
    }
  };

使用示例:

#include <iostream>
#include <iomanip>
#include <vector>
#include <list>

#include <iterator.h>

int
main() {
  using vector   = std::vector<int>;
  using list     = std::list<int>;
  using iterator = normal_forward_iterator_variant<const int, vector::const_iterator, list::const_iterator>;

  iterator iter;

  vector v{ 0, 1, 2 };
  iter = v.cbegin();
  std::cout << *iter++ << std::endl;
  std::cout << *iter << std::endl;
  std::cout << *++iter << std::endl;

  list l{ 3, 4, 5 };
  iter = l.cbegin();
  std::cout << *iter++ << std::endl;
  std::cout << *iter << std::endl;
  std::cout << *++iter << std::endl;
}

如预期的那样,输出为 1、2、...、6。

【讨论】:

  • 想想看,应该也包括一个iterator_concept 别名...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-30
  • 2013-02-14
  • 1970-01-01
相关资源
最近更新 更多