简体   繁体   English

C ++中的多态迭代器

[英]polymorphic iterators in C++

I'm trying to implement a polymorphic iterator in C++. 我正在尝试在C ++中实现多态迭代器。 Basically, I need this to be able to apply a filter, so that the iterator would skip some items depending on the associated condition. 基本上,我需要这个能够应用一个过滤器,以便迭代器可以跳过一些项目,具体取决于相关的条件。 So I made a GoF-like iterator with an abstract interface, this allows me to derive a filtered iterator from it and implement the required logic. 所以我使用抽象接口创建了一个类似GoF的迭代器,这允许我从中派生一个过滤迭代器并实现所需的逻辑。 I also prefer interface-based iterators over templated ones as they allow to hide implementation without leading to a mess of duck-typed templates. 我也更喜欢基于接口的迭代器而不是模板化的迭代器,因为它们允许隐藏实现而不会导致一堆鸭类模板。

However, polymorphic iterators cannot be returned by value (as opposed to STL iterators), so I have to pass pointers around, and this can easily become dangerous like in this case, which seems logical but leads to a memory leak: 但是,多态迭代器不能通过值返回(与STL迭代器相反),所以我必须传递指针,这很容易变得危险,就像在这种情况下,这似乎是合乎逻辑的,但会导致内存泄漏:

Iter* Collection::GetIter() {...} // new IterImpl
DoSomething(Iter*) {...} // doesn't do delete

DoSomething(Collection.GetIter()); // convenient, but wrong :\

The obvious solution is to use some kind of smart pointers to control iterators lifetime, but people often say that interfaces should be as simple and as general as possible, so smart pointers should probably be avoided there? 显而易见的解决方案是使用某种智能指针来控制迭代器的生命周期,但人们常说接口应尽可能简单和通用,因此应该避免使用智能指针?

If you have worked with polymorphic iterators in C++, how was this issue resolved? 如果你在C ++中使用过多态迭代器,那么这个问题是如何解决的? Or are template-based iterators the only "good" way of iteration in C++? 或者基于模板的迭代器是C ++中唯一“好”的迭代方式? Thanks. 谢谢。

The usual approach is to use compile-time polymorphism instead of runtime polymorphism; 通常的方法是使用编译时多态而不是运行时多态; this allows the compiler many more opportunities to optimize code using the iterator and generally is more idiomatic in modern C++. 这允许编译器有更多机会使用迭代器优化代码,并且通常在现代C ++中更惯用。

If you do need runtime polymorphic behavior, it's probably easiest to encapsulate the polymorphism within the iterator itself and not expose it externally. 如果确实需要运行时多态行为,那么将多态性封装在迭代器本身中并且不在外部公开它可能是最容易的。 You can accomplish this using a polymorphic function wrapper like function , found in Boost, C++ TR1, and C++0x. 您可以使用多态函数包装像完成这个function ,在加速中发现,C ++ TR1和C ++ 0x中。 I've provided an example here based on a filter iterator from one of my hobby projects: 我在这里提供了一个基于我的一个业余爱好项目的过滤器迭代器的例子:

template <typename ForwardIt>
class filter_iterator
    : public std::iterator<
          std::forward_iterator_tag, 
          typename std::iterator_traits<ForwardIt>::value_type>

{
public:

    typedef typename std::iterator_traits<ForwardIt>::value_type ValueType;
    typedef typename std::function<bool(ValueType)> FunctionType;

    filter_iterator() { }

    explicit filter_iterator(ForwardIt end)
        : it_(end), end_(end) 
    {
    }

    filter_iterator(ForwardIt it, ForwardIt end, FunctionType is_filtered) 
        : it_(it), end_(end), is_filtered_(is_filtered)
    { 
        skip_filtered_elements(); 
    }

    const ValueType& operator*()  const { return it_.operator*();  }
    const ValueType* operator->() const { return it_.operator->(); }

    filter_iterator& operator++()   
    { 
        ++it_; skip_filtered_elements(); return *this; 
    }

    filter_iterator operator++(int) 
    { 
        filter_iterator it(*this); ++*this; return it; 
    }


    friend bool operator==(const filter_iterator& lhs,
                           const filter_iterator& rhs)
    {
        return lhs.it_ == rhs.it_;
    }

    friend bool operator!=(const filter_iterator& lhs,
                           const filter_iterator& rhs)
    {
        return !(lhs == rhs);
    }

private:

    void skip_filtered_elements()
    {
        while (it_ != end_ && is_filtered_(*it_))
            std::advance(it_, 1);
    }

    ForwardIt it_;
    ForwardIt end_;

    std::function<bool(const ValueType&)> is_filtered_;
};

template <typename ForwardIt>
filter_iterator<ForwardIt> make_filter_iterator(ForwardIt end)
{
    return filter_iterator<ForwardIt>(end);
}

template <typename ForwardIt, typename Function>
filter_iterator<ForwardIt> make_filter_iterator(ForwardIt it, 
                                                ForwardIt end, 
                                                Function f)
{
    return filter_iterator<ForwardIt>(it, end, f);
}

Usage is straightforward. 用法很简单。 This example (using a C++0x lambda expression as the function type) demonstrates filtering odd numbers from a range: 此示例(使用C ++ 0x lambda表达式作为函数类型)演示了从一个范围中过滤奇数:

int main()
{
    std::array<int, 4> x = { 1, 2, 3, 4 };

    std::copy(make_filter_iterator(x.begin(), x.end(), [](int i) { return i % 2; }),
              make_filter_iterator(x.end()),
              std::ostream_iterator<int>(std::cout, " "));
}

There are two issues here: 这里有两个问题:

  • syntax: the STL assumes that iterators provide traits ( value_type , reference for example) which need match the actual item. 语法:STL假定迭代器提供需要与实际项匹配的traits( value_typereference )。
  • semantics: the iterators shall be copyable. 语义:迭代器应该是可复制的。

Remember that (in C++) an iterator is not a range, and therefore the ++ operation quickly gets messy, because you need to skip over some items, but (with a traditional implementation) you cannot know how many items are at your disposal... 请记住(在C ++中)迭代器不是范围,因此++操作很快就会变得混乱,因为您需要跳过某些项目,但是(使用传统实现)您无法知道有多少项目可供您使用。 ..

Therefore, if you want polymorphic iterators which follow the GOF interface, you'll have to forgo the use of STL algorithms. 因此,如果您需要遵循GOF接口的多态迭代器,则必须放弃使用STL算法。

That said, it's perfectly feasible to implement polymorphic iterators: 也就是说,实现多态迭代器是完全可行的:

struct IterBase
{
  virtual void increment() = 0;
  virtual void decrement() = 0;

  // others
};

class Iter
{
public:
  Iter& operator++() { base->increment(); return *this; }
  Iter operator++(int) { Iter tmp(*this); base->increment(); return tmp; }

  // others

private:
  std::unique_ptr<IterBase> base;
};

And then you'll need to write all the copy constructors, assignment operators and destructors to do the right thing... 然后你需要编写所有的复制构造函数,赋值运算符和析构函数来做正确的事情......

Without template polymorphism though, it's only worth it if your iterator is only ever meant to be used on the same type... 但是,如果没有模板多态,只有你的迭代器只能用于同一类型时,它才有价值...

A good solution I saw linked on Oli Charlesworth's question that didn't get much credit (at least, not as much as I thought it should). 我看到一个很好的解决方案与Oli Charlesworth的问题相关联,这个问题没有得到太多的信任(至少,没有我想象的那么多)。

class Iterator
{
public:
    SmartPointer<IteratorImplementation> ItrPtr;

    //Delegate methods to ItrPtr
}

You can then pass Iterator by value and defer methods to the contained smart pointer; 然后,您可以按值传递Iterator并将方法推迟到包含的智能指针; It is basically an iterator that implements the 'Strategy' pattern, and the strategy exhibits the polymorphic behavior. 它基本上是一个实现“策略”模式的迭代器,策略展示了多态行为。

It can be done using an iterator that contains a pointer in some form, and then passes on the functionality to the pointer. 它可以使用包含某种形式的指针的迭代器来完成,然后将该功能传递给指针。

You need to be very careful though doing this, and I have seen it done wrong many times (including falling for it myself once, then wondering why tests failed...) 虽然这样做你需要非常小心,我已经多次看到它做错了(包括我自己摔倒一次,然后想知道为什么测试失败......)

  • Do not use shared_ptr! 不要使用shared_ptr!

Fortunately I have not seen anyone above make the mistake of using shared_ptr, but it has the wrong semantics as when you copy an iterator you have 2 separate copies. 幸运的是,我没有看到上面的任何人犯了使用shared_ptr的错误,但它有错误的语义,就像复制迭代器时你有2个单独的副本。 But if they contain a shared_ptr and you advance one of the iterators, the other one will move with it - unexpected behaviour... 但是如果它们包含一个shared_ptr而你推进其中一个迭代器,那么另一个将随之移动 - 意外的行为......

Therefore you need to clone every time you copy, but fortunately with C++0x you can just move much of the time rather than clone. 因此,每次复制时都需要克隆,但幸运的是,使用C ++ 0x,您可以移动大部分时间而不是克隆。

You also need to know that operations will hit the v-table for each iteration which may cause it to run slower than if you made the "macro" methods polymorphic (and implement perhaps with a template so you don't need to rewrite the code). 您还需要知道每次迭代时操作都会遇到v-table,这可能导致它运行速度比使“宏”方法变为多态(并且可能使用模板实现,因此您不需要重写代码) )。

People do say that interfaces should be as simple and as general as possible. 人们会说接口应该尽可能简单和通用。 In your case, you describe a raw pointer as something that is not 'possible'. 在您的情况下,您将原始指针描述为不可能的东西。 So, I'd suggest your obvious solution of using a smart pointer is the most simple and general technique that is possible. 因此,我建议您使用智能指针的明显解决方案是最简单和最通用的技术。

To keep this smart pointer as simple and general as possible, I'd go with one of the stl provided smart pointers as they are the most ubiquitous. 为了使这个智能指针尽可能简单和通用,我会选择一个stl提供的智能指针,因为它们是最普遍的。

Been there. 到过那里。 Done that. 做完了。

What you can do is hide your iterator interface behind another iterator. 你可以做的是隐藏你的迭代器接口在另一个迭代器后面。 Suppose that you have several kind of iterators that all hide behind the IIterator interface. 假设您有几种全部隐藏在IIterator接口后面的迭代器。

Then write another Iterator-like class, eg MyIterator, which contains a pointer to IIterator, and which simply forwards all calls to IIterator, like this: 然后编写另一个类似迭代器的类,例如MyIterator,它包含一个指向IIterator的指针,它只是将所有调用转发给IIterator,如下所示:

template <typename T>
class MyIterator
    {
    public:
       MyIterator() : m_iterator(nullptr) {}
       MyIterator(IIterator *it) : m_iterator(it) {}
       MyIterator &operator++()
          {
          if (m_iterator) m_iterator->operator++();
          return *this;
          }
       T &operator*() const
          {
          if (m_iterator) return m_iterator->operator*();
          else            throw an exception?
          }
    private
       IIterator *m_iterator;
    };

This example is far from complete, but you should get the idea. 这个例子远非完整,但你应该明白这个想法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM