【问题标题】:how to Implement iterator in derived class in c++?如何在 C++ 派生类中实现迭代器?
【发布时间】:2016-07-20 16:00:56
【问题描述】:
class List{

    public:
        class ListIterator{

            public:

        };
        typedef ListIterator iterator;
        virtual iterator begin() = 0;
};
class ArrayList:public List{
    public:
        class ArrayListIterator{

            public:

        };
        typedef ArrayListIterator iterator;
        iterator begin(){

        }
};
class LinkedList:public List{

    public:
        class LinkedListIterator{

            public:

        };
        typedef LinkedListIterator iterator;
        iterator begin(){

        }
};

我想以这种方式实现迭代器。但是编译器显示错误

[Error] invalid covariant return type for 'virtual ArrayList::iterator ArrayList::begin()'
[Error] overriding 'virtual List::iterator List::begin()'

LinkedList 也是如此。

当我在堆栈溢出中搜索相同的问题时,我得到了这个解决方案

Iterator for custom container with derived classes 我得到了两个解决方案

1 . implement iterator without using runtime polymorphism
2 . implement seperator iterator class.

但我想知道是否有任何可能的方法使迭代器成为 ArrayList 、 LinkedList 和 List 的内部类??

【问题讨论】:

  • 你设计的重点是什么?这看起来像是一个习惯了 C#/Java 的人的设计,他讨厌值类型,喜欢编写 C++ 代码的接口。
  • 我想用List接口设计一个ArrayList类和LinkedList类。
  • 所以,你的设计重点就是你的设计。这不是设计的理由。进行设计的一个原因是该设计用于的目的。
  • 标准库容器不使用基于继承的多态性。忽略这一事实后果自负。
  • 不,如果你愿意,你可以使用多态性来做到这一点,但你需要注意,如果你想将你的类与 stl 一起使用,这不是 stl 的工作方式

标签: c++ inheritance iterator


【解决方案1】:

您需要 ArrayListIterator 从 ListIterator 派生。

在派生类中begin返回的类型不是从基List类中begin返回的类型派生的。

这就是为什么错误说它们不是协变的。

【讨论】:

  • 将 ListIterator 派生为 ArrayListIterator 后给出相同的错误。
  • 正如 lorro 指出的那样,您还需要开始按指针(或引用)返回,以便它可以多态地工作。
  • -1 为什么?原帖中的消息错误是由解释的原因引起的。需要很长时间才能确定整个想法是否合理。
  • 我不是-1,但您的回答并不能解决 OP 的问题。在 OP 进行您建议的更改后,代码仍然无法编译。将指针建议折叠起来,您就会到达某个地方,但这不在您的答案中。这意味着您要告诉 OP 手动管理指针拥有的资源,这本身可能值得 -1!
  • 是的,我的答案在实现 std 集合接口的上下文中失败,因为 begin 不会通过标准集合中的指针/引用返回,我打算编辑建议使用非返回对象- 虚拟接口模式来包装特定于容器的迭代实现,但你打败了我。
【解决方案2】:

你需要抛弃基于继承的多态性。标准库和for(:) 循环中的 C++ 迭代假定基于值的迭代器,而基于继承的多态性不适用于基于值的迭代器。

您应该使用基于类型擦除的多态,而不是基于继承的多态。这需要一些样板,因为在 C++ 中发现抽象处理可迭代范围的需求并不常见,而且通常会严重影响性能。

但我会告诉你怎么做。

基于类型擦除的多态性类似于std::function

类型擦除多态性的一种具体方法:

您决定要支持的接口。将此称为您的“类型擦除目标”。您的类型擦除目标不是具有virtual=0 的类,而是一组您想要支持的函数、方法和操作,以及它们的签名,以及每个功能的描述.

然后您编写一个值类实现该接口(同样,无继承),该接口包含一个pImpl(参见pImpl 模式),它将其操作分派到(pImpl不需要匹配相同的接口,它只需要您可以实现操作的原语。在这里最小化是值得的)。

pImpl 类型确实具有virtual 方法和=0 抽象方法。

然后你编写一个构造函数或工厂函数,它接受一个支持你想要的接口的对象,并生成pImpl 的具体实例,然后将值类包装在它周围。

假设类型擦除目标是“打印到流”。

我的类型擦除目标是 std::ostream& << foo 工作,并打印东西。

struct printable_view {
  // dispatch to pimpl:
  friend std::ostream& operator<<( std::ostream& o, printable_view const& p ) {
    p->print(o);
    return o;
  }
  // pimpl:
private:
  struct printable_view_impl {
    virtual ~printable_view_impl() {}
    virtual void print(std::ostream& o) = 0;
  };
  std::unique_ptr<printable_view_impl> pImpl;

private:
  template<class T>
  struct printer:printable_view_impl {
    printer( T const* p ):ptr(p) {}
    T const* ptr; // just a view, no ownership
    virtual void print( std::ostream& o ) final override {
      o << *ptr;
    }
  };
public:
  // create a pimpl:
  printable_view(printable_view&&)=default;
  printable_view(printable_view const&)=delete;
  printable_view()=delete;
  template<class T>
  printable_view( T const& t ):
    pImpl( std::make_unique<printer<T>>( std::addressof(t) ) )
  {}
};

可能有错别字,但你明白了。

Boost has a generic iterator and range,如果您想找到示例实现,您的基类可以返回。

使用基于类型擦除的迭代会对性能产生重大影响:您最终会得到与 C#/Java 代码一样慢的 C++ 代码。

在您的情况下,您需要按照标准的要求获取iterator 要求的所有内容(复制、递增、移动、取消引用等),然后像我复制的那样删除它。在这种情况下,它不是视图,因此您的 impl impl 可能会持有 T 而不是 T*

这是一个非常简单的玩具for(:),它支持玩具伪迭代器,它允许您的List 基数在for(:) 循环中使用。

template<class T>
struct any_iterator_sorta {
  T operator*()const { return pImpl->get(); }
  void operator++() { pImpl->next(); }
  any_iterator_sorta(any_iterator_sorta const& o):
    any_iterator_sorta( o.pImpl?any_iterator_sorta(o.pImpl->clone()):any_iterator_sorta() )
  {}
  friend bool operator==(any_iterator_sorta const& lhs, any_iterator_sorta const& rhs ) {
    if (!lhs.pImpl || ! rhs.pImpl)
      return lhs.pImpl == rhs.pImpl;
    return lhs.pImpl->equals( *rhs.pImpl );
  }
  friend bool operator!=(any_iterator_sorta const& lhs, any_iterator_sorta const& rhs ) {
    return !(lhs==rhs);
  }
  any_iterator_sorta(any_iterator_sorta&& o) = default;
  any_iterator_sorta() = default;

  any_iterator_sorta& operator=(any_iterator_sorta const& o) {
    any_iterator_sorta tmp=o;
    std::swap(tmp.pImpl, o.pImpl);
    return *this;
  }
  any_iterator_sorta& operator=(any_iterator_sorta&& o) = default;
private:
  struct pimpl {
    virtual ~pimpl() {}
    virtual void next() = 0;
    virtual T get() const = 0;
    virtual std::unique_ptr< pimpl > clone() const = 0;
    virtual bool equals( pimpl const& rhs ) const = 0;
  };
  std::unique_ptr< pimpl > pImpl;
  template<class It>
  struct pimpl_impl:pimpl {
    It it;
    virtual void next() final override { ++it; }
    virtual T get() const final override { return *it; }
    virtual std::unique_ptr< pimpl > clone() const final override {
      return std::make_unique<pimpl_impl>( it );
    }
    virtual bool equals( pimpl const& rhs ) const final override {
      if (auto* r = dynamic_cast<pimpl_impl const*>(&rhs))
        return it == r->it;
      return false;
    }
    pimpl_impl( It in ):it(std::move(in)) {}
  };
  any_iterator_sorta( std::unique_ptr< pimpl > pin ):pImpl(std::move(pin)) {}
public:
  template<class It,
    std::enable_if_t< !std::is_same<It, any_iterator_sorta>{}, int>* =nullptr
  >
  any_iterator_sorta( It it ):
    pImpl( std::make_unique<pimpl_impl<It>>( std::move(it) ) )
  {}       
};

如果您的接口类返回一个any_iterator_sorta&lt;T&gt;,其中T 是您迭代的类型,并且子类执行相同的操作(但返回的类支持++*、复制构造和@987654347 @),它会表现出多态的值。

any_iterator_sorta 是一个 C++ 伪迭代器,足以在 for(:) 循环中工作,但不满足标准要求的真正 C++ 迭代器的所有公理。

live example

测试用例:

void test( any_iterator_sorta<int> begin, any_iterator_sorta<int> end )
{
  for (auto it = begin; it != end; ++it) {
    std::cout << *it << '\n';
  }
}

std::vector<int> v{1,2,3};
std::list<int> l{10,11};

test( begin(v), end(v) );
test( begin(l), end(l) );

相同的代码,使用两种不同的迭代器实现进行迭代。

具体来说,假设您的代码遍历ints:

    virtual any_iterator_sorta<int> begin() = 0;
    virtual any_iterator_sorta<int> end() = 0;

List。然后在ArrayList

   any_iterator_sorta<int> begin() final override {
     return ArrayListIterator{};
   }
   any_iterator_sorta<int> end() final override {
     return ArrayListIterator{};
   }

最后,实现ArrayListIterator

   class ArrayListIterator{

   public:
     int operator*() const { return 0; }
     bool operator==( ArrayListIterator const& o ){return true;}
     void operator++() { /* do nothing for now */ }
   };

上面只包含 4 个必需操作的“存根”版本(复制构造、==++ 和一元*),因此ArrayList 将在 C++ for(:) 循环中显示为“空” .

【讨论】:

  • 因为概念迭代和多态并不是不兼容的。确实,stl 不能以这种方式工作,但断言需要抛弃多态性的想法是不正确的。能够针对您知道将迭代特定类型的集合进行编码通常很有用,而不必知道集合类型是否是向量/列表/数组。如果这些迭代器用于 stl 算法(OP 没有说明),那就不同了。
  • @ROX C++ 标准迭代器是值类型。值类型不是(基于继承的)多态的。如果 OP 使用术语“迭代器”来表示与 C++ 中的“迭代器”不同的含义,我会感到惊讶,并建议 OP 更改他们对术语的使用。我描述了如何执行多态值类型,以匹配您必须执行的 C++ 标准“迭代”概念。这包括for(:) 循环支持,超出了std 算法。
  • 我认为迭代器是一种模式——SOs 迭代器标签上的“about”很好地涵盖了这一点。您对在谈论迭代器模式时如何“改变他们对术语的使用”有什么具体建议,但不一定是遵循 stl 设计的具体建议?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-07
  • 1970-01-01
  • 2021-03-01
  • 1970-01-01
相关资源
最近更新 更多