C ++中的多态迭代器

时间:2011-01-31 15:32:55

标签: c++ iterator polymorphism

我正在尝试在C ++中实现多态迭代器。基本上,我需要这个能够应用一个过滤器,以便迭代器可以跳过一些项目,具体取决于相关的条件。所以我创建了一个带有抽象接口的GoF-like迭代器,这允许我从中派生一个过滤的迭代器并实现所需的逻辑。我也更喜欢基于接口的迭代器而不是模板化迭代器,因为它们允许隐藏实现而不会导致一堆鸭模型模板。

但是,多态迭代器不能通过值返回(与STL迭代器相反),因此我必须传递指针,这很容易变得像在这种情况下一样危险,这似乎是合乎逻辑的,但会导致内存泄漏:< / p>

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

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

显而易见的解决方案是使用某种智能指针来控制迭代器的生命周期,但是人们常说接口应该尽可能简单和通用,因此应该避免使用智能指针吗?

如果你在C ++中使用过多态迭代器,那么这个问题是如何解决的?或者基于模板的迭代器是C ++中唯一“好”的迭代方式?感谢。

6 个答案:

答案 0 :(得分:9)

通常的方法是使用编译时多态而不是运行时多态;这允许编译器有更多机会使用迭代器优化代码,并且在现代C ++中通常更为惯用。

如果确实需要运行时多态行为,那么将多态性封装在迭代器本身中并且不在外部公开它可能是最容易的。您可以使用像function这样的多态函数包装器来实现这一点,可以在Boost,C ++ TR1和C ++ 0x中找到它。我在这里提供了一个基于我的业余爱好项目中的过滤器迭代器的示例:

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

用法很简单。此示例(使用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, " "));
}

答案 1 :(得分:4)

这里有两个问题:

  • 语法:STL假定迭代器提供需要与实际项匹配的特征(value_typereference)。
  • 语义:迭代器应该是可复制的。

请记住(在C ++中)迭代器不是范围,因此++操作很快就会变得混乱,因为你需要跳过一些项目,但是(使用传统的实现)你不知道有多少物品随时为您服务......

因此,如果你想要遵循GOF接口的多态迭代器,你将不得不放弃使用STL算法。

那就是说,实现多态迭代器是完全可行的:

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;
};

然后你需要编写所有的拷贝构造函数,赋值运算符和析构函数来做正确的事......

虽然没有模板多态,但只有你的迭代器只能用于同一类型时,它才有价值......

答案 2 :(得分:2)

我看到一个很好的解决方案,与Oli Charlesworth的问题相关联,但没有得到多少赞誉(至少,没有我想象的那么多)。

class Iterator
{
public:
    SmartPointer<IteratorImplementation> ItrPtr;

    //Delegate methods to ItrPtr
}

然后,您可以按值传递Iterator并将方法推迟到包含的智能指针;它基本上是一个实现“策略”模式的迭代器,策略展示了多态行为。

答案 3 :(得分:2)

可以使用包含某种形式的指针的迭代器来完成,然后将该功能传递给指针。

尽管这样做你需要非常小心,我已经多次看错了(包括我自己摔倒一次,然后想知道为什么测试失败了......)

  • 不要使用shared_ptr!

幸运的是,我没有看到上面的任何人犯了使用shared_ptr的错误,但它有错误的语义,就像你复制迭代器时你有2个单独的副本。但是如果它们包含一个shared_ptr而你推进其中一个迭代器,那么另一个将随之移动 - 意外的行为......

因此,每次复制时都需要克隆,但幸运的是,使用C ++ 0x,您可以移动大部分时间而不是克隆。

你还需要知道每次迭代时操作都会遇到v-table,这可能会导致它比你使“宏”方法多态的运行速度慢(并且可能使用模板实现,所以你不需要重写代码。)

答案 4 :(得分:0)

人们会说接口应该尽可能简单和通用。在您的情况下,您将原始指针描述为不可能的东西。所以,我建议你使用智能指针的明显解决方案是最简单和最通用的技术。

为了让这个智能指针尽可能简单和通用,我会选择其中一个提供的智能指针,因为它们是最普遍的。

答案 5 :(得分:0)

去过那儿。做完了。

您可以做的是将迭代器接口隐藏在另一个迭代器后面。 假设你有几种迭代器都隐藏在IIterator接口后面。

然后编写另一个类似迭代器的类,例如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;
    };

这个例子远非完整,但你应该明白这一点。