C ++条件单向迭代器

时间:2019-05-14 15:07:06

标签: c++ c++11

我想实现类似下面的伪代码:

string foo;  // or vector<int> foo;
auto itr = bar?  foo.begin() : foo.rbegin();
auto end = bar?  foo.end() : foo.rend();
for (  ; itr != end; ++itr) {
// SomeAction ...
}

也就是说,我想根据某些条件itrbar设置为正向迭代器或反向迭代器,以正向或反向扫描。

由于正向迭代器和反向迭代器具有不同的类型,因此类似的代码显然行不通。

请注意,我不想分成两个循环,因为像// SomeAction这样的代码将被复制。

我该怎么做? 首选使用C ++ 11和/或更低版本的答案。

另外,请详细说明字符串和向量的解决方案是否不同。

5 个答案:

答案 0 :(得分:27)

我将逻辑放在两个迭代函数中:

<template typename Iter>
void do_stuff(Iter first, Iter last)
{
    for(; first != last; ++first)
    {
        // Do logic
    }
}

bar ? do_stuff(foo.begin(), foo.end()) : do_stuff(foo.rbegin(), foo.rend());

答案 1 :(得分:16)

对于大多数(如果不是全部)容器,正向和反向迭代器是不同的类型,因此不幸的是,如果这是运行时决定,则不能简单地通过使用auto将它们分配给同一变量。

一种选择是将它们的使用转移到模板函数中:

template<class Iterator> void loop(Iterator begin, Iterator end)
{
    for (auto itr = begin; itr != end; ++itr) { ... }
}

if (bar) loop(foo.begin(), foo.end());
else loop(foo.rbegin(), foo.rend());

在较新版本的C ++(C ++ 14和更高版本,而不是C ++ 11)中,通过使用auto作为参数类型,循环函数可以是lambda。

auto loop = [](auto begin, auto end)
{
    for (auto itr = begin; itr != end; ++itr) { ... }
};

另一个选择,尽管涉及更多一些,是使包装器类型可以包含迭代器或反向迭代器,并且至少像比较,增量和取消引用运算符一样充当迭代器本身。

答案 2 :(得分:9)

  

我不想分成两个循环,因为// SomeAction之类的代码将重复。

将动作放入lambda中。

auto lambda = [&](char &val) // `ElementType &`
{
    // ...
};

if (bar)
{
    for (auto &val : foo)
        lambda(val);
}
else
{
    for (auto it = foo.rbegin(); it != foo.rend(); it++)
        lambda(*it);
}

或者,使用索引而不是迭代器。这仅适用于允许随机访问的容器。

std::size_t i, end, step;
if (bar)
{
    i = 0;
    end = foo.size();
    step = 1;
}
else
{
    i = foo.size() - 1;
    end = -1;
    step = -1;
}

for (; i != end; i += step)
{
    // ...
}

答案 3 :(得分:9)

一种选择是为可与任何迭代器一起使用的循环编写函数模板。然后有条件地调用模板的一个实例或另一个。其他答案已经显示了如何执行此操作的示例。

顺便说一句,循环模板可能已经存在于<algorithm>标头中,这取决于您在做什么。您可以使用(但不限于)std::for_eachstd::accumulatestd::remove例如:

auto body = [captures,needed,by,some,action](char c) {
    // SomeAction ...
};
if (bar)
    std::for_each(foo.begin(),  foo.end(),  body);
else
    std::for_each(foo.rbegin(), foo.rend(), body);

如果循环的主体可以在此上下文之外重用,那么也可以使用命名函数,但前提是不需要捕获。通过捕获,可以使用命名的仿函数类型,但这涉及很多样板。


另一个选择是使用类型擦除迭代器适配器。它的运行时成本很小,在这里可能没有必要。但是,如果人们遇到一个更合适的问题,那就更有用了。

本质上,这种适配器是模板化迭代器,而std::function则是模板化函子参数。它消除了对模板的需求,这对于抽象接口尤其有用。但是不幸的是,标准库没有提供这种迭代器适配器。

迭代器适配器的替代产品是范围适配器(也不在标准库中):

using t_erase = boost::adaptors::type_erased<>;
auto range = bar
    ? boost::make_iterator_range(foo.begin(),  foo.end())  | t_erase()
    : boost::make_iterator_range(foo.rbegin(), foo.rend()) | t_erase();
for(char c : range) {
    // SomeAction ...
}

答案 4 :(得分:2)

使用抽象这种功能的迭代器类。

迭代器具有3个基本功能:

  • 增量
  • 取消引用
  • 检查是否平等

我们可以将这些用作创建抽象该行为的界面的准则。单独使用此接口有点麻烦,但是我们可以使用它来构建包装器类GenericIterator,该包装器类可以自动分配任何其他迭代器类型。

GenericIteratorA ready-made general purpose solution

有可能编写一个GenericIterator类,可以从几乎所有集合中为迭代器分配迭代器,包括反向迭代器。

int main() {
    bool iterate_forward;
    std::cin >> iterate_forward; 

    std::vector<int> values { 1, 2, 3 };

    GenericIterator<int&> begin, end; 

    if(iterate_forward) {
        begin = values.begin(); 
        end = values.end(); 
    } else {
        begin = values.rbegin();
        end = values.rend(); 
    }

    // Print out the values
    for(; begin != end; ++begin) {
        std::cout << *begin << " ";
    }
}

您可以下载完整的代码from this github repo,我将根据需要进行更新和改进。

对性能的评论GenericIterator

在功能方面,GenericIterator为您提供了可能需要的一切。重量轻;而且很方便如果您的代码需要从std::list或其他向量中读取,则很容易实现重新用途。

但是,由于运行时多态性的基本限制,编译器内联虚拟方法调用要困难得多。这意味着GenericIterator比其他解决方案要承担更多的运行时开销。

在可能的情况下,建议优先使用静态多态性和模板而不是运行时多态性。如果可以的话,请使用Mark B's solution之类的方法,这样最终会提高性能。

附录

迭代器接口定义。。该类用于实现GenericIteratorGenericIterator包含一个指向IteratorBase的指针,该指针用于实现运行时多态。多亏了clone()方法,GenericIterator仍然可以按预期复制和移动。

template <class Value>
class IteratorBase
{
   public:
    virtual Value         operator*() const                     = 0;
    virtual IteratorBase& operator++()                          = 0;
    virtual bool          operator!=(IteratorBase const&) const = 0;
    virtual bool          operator==(IteratorBase const&) const = 0;

    // We need this function for making copies of the iterator
    virtual IteratorBase* clone() const = 0;
    virtual ~IteratorBase()             = default;
};

实现IteratorBase的具体类。此类实现IteratorBase中定义的行为。它包含的迭代器是要迭代的集合返回的 actual 迭代器。对于您来说,它可能是std::vector::iteratorstd::vector::reverse_iterator

template <class Iter, class Value>
class IteratorDerived : public IteratorBase<Value>
{
    Iter it;

   public:
    IteratorDerived() = default;
    IteratorDerived(Iter it) : it(it) {}
    IteratorDerived(IteratorDerived const&) = default;
    IteratorDerived(IteratorDerived&&)      = default;

    Value                operator*() const override { return *it; }
    IteratorBase<Value>& operator++() override
    {
        ++it;
        return *this;
    }

    bool operator!=(IteratorBase<Value> const& other) const override
    {
        auto* derived = dynamic_cast<IteratorDerived const*>(&other);
        return derived == nullptr || it != derived->it;
    }
    bool operator==(IteratorBase<Value> const& other) const override
    {
        auto* derived = dynamic_cast<IteratorDerived const*>(&other);
        return derived != nullptr && it == derived->it;
    }
    IteratorBase<Value>* clone() const override
    {
        return new IteratorDerived(*this);
    }
};

GenericIterator实现。这是GenericIterator的实际实现,基于IteratorBaseIteratorDerived。给GenericIterator的所有迭代器都包装在相应的IteratorDerived中,然后将其分配给IteratorBase指针。

template <class Value>
class GenericIterator
{
    std::unique_ptr<IteratorBase<Value>> iterator;

   public:
    using value_type = typename std::remove_reference<Value>::type; 
    using reference = Value;

    GenericIterator() = default;
    GenericIterator(GenericIterator const& it) : iterator(it.iterator->clone())
    {
    }
    GenericIterator(GenericIterator&&) = default;

    // Creates a GenericIterator from an IteratorBase
    explicit GenericIterator(IteratorBase<Value> const& it)
      : iterator(it.clone())
    {
    }

    // Creates a GenericIterator from an IteratorDerived
    template <class Iter>
    explicit GenericIterator(IteratorDerived<Iter, Value> const& it)
      : iterator(it.clone())
    {
    }

    // Creates a GenericIterator by wrapping another Iter
    template <class Iter>
    GenericIterator(Iter it) : iterator(new IteratorDerived<Iter, Value>(it))
    {
    }

    GenericIterator& operator=(GenericIterator const& it)
    {
        iterator = std::unique_ptr<IteratorBase<Value>>(it.iterator->clone());
        return *this;
    }
    GenericIterator& operator=(GenericIterator&&) = default;

    Value            operator*() const { return *(*iterator); }
    GenericIterator& operator++()
    {
        ++(*iterator);
        return *this;
    }
    void operator++(int) {
        ++(*iterator);
    }

    bool operator==(GenericIterator const& other) const
    {
        return *iterator == *other.iterator;
    }
    bool operator!=(GenericIterator const& other) const
    {
        return *iterator != *other.iterator;
    }
};