Roman L
Roman L

Reputation: 3056

polymorphic iterators in C++

I'm trying to implement a polymorphic iterator in 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. 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:

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? Or are template-based iterators the only "good" way of iteration in C++? Thanks.

Upvotes: 21

Views: 7202

Answers (6)

Matthieu M.
Matthieu M.

Reputation: 300299

There are two issues here:

  • syntax: the STL assumes that iterators provide traits (value_type, reference for example) which need match the actual item.
  • 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.

Therefore, if you want polymorphic iterators which follow the GOF interface, you'll have to forgo the use of STL algorithms.

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.

Upvotes: 4

CashCow
CashCow

Reputation: 31445

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!

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. But if they contain a shared_ptr and you advance one of the iterators, the other one will move with it - unexpected behaviour...

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.

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).

Upvotes: 3

James McNellis
James McNellis

Reputation: 355297

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++.

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. 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:

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, " "));
}

Upvotes: 10

Patrick
Patrick

Reputation: 23629

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.

Then write another Iterator-like class, e.g. MyIterator, which contains a pointer to IIterator, and which simply forwards all calls to IIterator, like this:

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.

Upvotes: 0

James
James

Reputation: 5425

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).

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; It is basically an iterator that implements the 'Strategy' pattern, and the strategy exhibits the polymorphic behavior.

Upvotes: 2

Scott Langham
Scott Langham

Reputation: 60411

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.

Upvotes: 0

Related Questions