C++中的多态迭代器

ndasle7k  于 2023-02-06  发布在  其他
关注(0)|答案(6)|浏览(178)

我正在尝试用C++实现多态迭代器。基本上,我需要它能够应用过滤器,以便迭代器根据相关条件跳过一些项。所以我做了一个带有抽象接口的GoF-like迭代器,这允许我从它派生一个过滤迭代器并实现所需的逻辑。基于模板的迭代器,因为它们允许隐藏实现而不会导致duck-typed模板的混乱。
然而,多态迭代器不能通过值返回(与STL迭代器相反),所以我必须传递指针,这很容易变得危险,就像本例一样,这看起来很合理,但会导致内存泄漏:

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

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

显而易见的解决方案是使用某种智能指针来控制迭代器的生存期,但人们常说接口应该尽可能简单和通用,那么智能指针大概应该在那里避免吧?
如果你在C中使用过多态迭代器,这个问题是如何解决的?或者基于模板的迭代器是C中唯一“好”的迭代方式吗?谢谢。

dnph8jn4

dnph8jn41#

通常的方法是使用编译时多态而不是运行时多态;这允许编译器有更多的机会使用迭代器优化代码,并且通常在现代C中更惯用。
如果您确实需要运行时多态行为,最简单的方法可能是将多态性封装在迭代器本身中,而不将其暴露在外部。您可以使用多态函数 Package 器(如function)来实现这一点,在Boost、C
TR 1和C++0x中都可以找到它。我在这里提供了一个示例,该示例基于我的一个业余项目中的filter迭代器:

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

r1zhe5dt2#

这里有两个问题:

  • 语法:STL假设迭代器提供需要匹配实际项的traits(例如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;
};

然后你需要写所有的复制构造函数,赋值操作符和析构函数来做正确的事情。
如果没有模板多态性,那么只有当迭代器只用于同一类型时,才值得使用模板多态性。

yi0zb3m4

yi0zb3m43#

这可以使用迭代器来完成,迭代器包含某种形式的指针,然后将功能传递给指针。
你需要非常小心,虽然这样做,我已经看到它做错了很多次(包括下降到它自己一次,然后想知道为什么测试失败...)

  • 不要使用shared_ptr!

幸运的是,我没有看到上面的任何人犯使用shared_ptr的错误,但它有错误的语义,当你复制一个迭代器时,你有两个独立的副本。但如果它们包含一个shared_ptr,你推进其中一个迭代器,另一个将随之移动-意外的行为...
因此,每次复制时都需要克隆,但幸运的是,使用C++0x,大部分时间可以移动而不是克隆。
您还需要知道,每次迭代的操作都会命中v-table,这可能会导致它的运行速度比您使“宏”方法多态(并且可能使用模板实现,这样您就不需要重写代码)的情况下要慢。

vs3odd8k

vs3odd8k4#

我看到的一个很好的解决方案与奥利·查尔斯沃思的问题有关,但没有得到多少信任(至少,没有我想象的那么多)。

class Iterator
{
public:
    SmartPointer<IteratorImplementation> ItrPtr;

    //Delegate methods to ItrPtr
}

然后可以按值传递Iterator并将defer方法传递给包含的智能指针;它基本上是一个实现“Strategy”模式的迭代器,并且策略表现出多态行为。

gt0wga4j

gt0wga4j5#

人们总是说接口应该尽可能的简单和通用。在你的例子中,你把一个原始指针描述为不可能的东西。所以,我建议你使用一个智能指针的明显解决方案是可能的最简单和通用的技术。
为了使这个智能指针尽可能的简单和通用,我会使用stl提供的一个智能指针,因为它们是最普遍的。

gojuced7

gojuced76#

我也经历过。
你能做的就是把你的迭代器接口隐藏在另一个迭代器后面,假设你有几种类型的迭代器,它们都隐藏在迭代器接口后面。
然后编写另一个类似Iterator的类,例如MyIterator,它包含一个指向Iterator的指针,并将所有调用转发给Iterator,如下所示:

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

这个例子还远未完成,但是您应该已经了解了这个概念。

相关问题