C++中等价于Python生成器模式

158 投票
13 回答
80282 浏览
提问于 2025-04-17 11:55

我有一些示例的Python代码,我需要在C++中模仿一下。我并不需要特定的解决方案(比如基于协程的yield解决方案,虽然这些也可以接受),我只是想以某种方式重现它的语义。

Python

这是一个基本的序列生成器,明显太大了,无法存储一个完整的版本。

def pair_sequence():
    for i in range(2**32):
        for j in range(2**32):
            yield (i, j)

目标是保持上面序列的两个实例,并以半同步的方式逐块迭代。在下面的例子中,first_pass使用成对的序列来初始化缓冲区,而second_pass重新生成完全相同的序列,并再次处理缓冲区。

def run():
    seq1 = pair_sequence()
    seq2 = pair_sequence()

    buffer = [0] * 1000
    first_pass(seq1, buffer)
    second_pass(seq2, buffer)
    ... repeat ...

C++

我在C++中找到的唯一解决方案是用C++的协程来模仿yield,但我还没有找到好的参考资料来了解怎么做。我也对这个问题的其他(非通用)解决方案感兴趣。我没有足够的内存来在两次处理之间保存序列的副本。

13 个回答

25

因为Boost.Coroutine2现在对这个问题的支持非常好(我发现这个库是因为我想解决同样的yield问题),所以我在这里分享一段符合你原意的C++代码:

#include <stdint.h>
#include <iostream>
#include <memory>
#include <boost/coroutine2/all.hpp>

typedef boost::coroutines2::coroutine<std::pair<uint16_t, uint16_t>> coro_t;

void pair_sequence(coro_t::push_type& yield)
{
    uint16_t i = 0;
    uint16_t j = 0;
    for (;;) {
        for (;;) {
            yield(std::make_pair(i, j));
            if (++j == 0)
                break;
        }
        if (++i == 0)
            break;
    }
}

int main()
{
    coro_t::pull_type seq(boost::coroutines2::fixedsize_stack(),
                          pair_sequence);
    for (auto pair : seq) {
        print_pair(pair);
    }
    //while (seq) {
    //    print_pair(seq.get());
    //    seq();
    //}
}

在这个例子中,pair_sequence不需要额外的参数。如果需要的话,可以使用std::bind或者一个lambda表达式来生成一个只接受一个参数(类型为push_type)的函数对象,然后再把它传给coro_t::pull_type的构造函数。

63

在C++中,有一种叫做迭代器的东西,但要自己实现一个迭代器并不简单。你需要查看一些关于迭代器的概念,并仔细设计新的迭代器类来符合这些要求。幸运的是,Boost库提供了一个叫做iterator_facade的模板,这可以帮助你实现迭代器和兼容迭代器的生成器。

有时候,你可以使用无栈协程来实现一个迭代器。

另外,建议你看看这篇文章,里面提到了Christopher M. Kohlhoff的一个switch技巧,以及Oliver Kowalke的Boost.Coroutine。Oliver Kowalke的工作是对Boost.Coroutine(由Giovanni P. Deretta开发)的后续研究。

另外,我觉得你也可以用lambda表达式来写一种生成器:

std::function<int()> generator = []{
  int i = 0;
  return [=]() mutable {
    return i < 10 ? i++ : -1;
  };
}();
int ret = 0; while ((ret = generator()) != -1) std::cout << "generator: " << ret << std::endl;

或者用一个函数对象来实现:

struct generator_t {
  int i = 0;
  int operator() () {
    return i < 10 ? i++ : -1;
  }
} generator;
int ret = 0; while ((ret = generator()) != -1) std::cout << "generator: " << ret << std::endl;

最后,这里有一个用Mordor协程实现的生成器:

#include <iostream>
using std::cout; using std::endl;
#include <mordor/coroutine.h>
using Mordor::Coroutine; using Mordor::Fiber;

void testMordor() {
  Coroutine<int> coro ([](Coroutine<int>& self) {
    int i = 0; while (i < 9) self.yield (i++);
  });
  for (int i = coro.call(); coro.state() != Fiber::TERM; i = coro.call()) cout << i << endl;
}
111

在C++中,生成器其实是以另一种名字存在的:输入迭代器。比如,从std::cin读取数据就像是有一个生成器来生成char字符。

你只需要明白生成器的作用:

  • 有一堆数据:局部变量定义了一个状态
  • 有一个初始化的方法
  • 有一个“下一个”的方法
  • 有一种方式来表示结束

在你简单的例子中,这些概念都很简单:

struct State { unsigned i, j; };

State make();

void next(State&);

bool isDone(State const&);

当然,我们会把它包装成一个合适的类:

class PairSequence:
    // (implicit aliases)
    public std::iterator<
        std::input_iterator_tag,
        std::pair<unsigned, unsigned>
    >
{
  // C++03
  typedef void (PairSequence::*BoolLike)();
  void non_comparable();
public:
  // C++11 (explicit aliases)
  using iterator_category = std::input_iterator_tag;
  using value_type = std::pair<unsigned, unsigned>;
  using reference = value_type const&;
  using pointer = value_type const*;
  using difference_type = ptrdiff_t;

  // C++03 (explicit aliases)
  typedef std::input_iterator_tag iterator_category;
  typedef std::pair<unsigned, unsigned> value_type;
  typedef value_type const& reference;
  typedef value_type const* pointer;
  typedef ptrdiff_t difference_type;

  PairSequence(): done(false) {}

  // C++11
  explicit operator bool() const { return !done; }

  // C++03
  // Safe Bool idiom
  operator BoolLike() const {
    return done ? 0 : &PairSequence::non_comparable;
  }

  reference operator*() const { return ij; }
  pointer operator->() const { return &ij; }

  PairSequence& operator++() {
    static unsigned const Max = std::numeric_limts<unsigned>::max();

    assert(!done);

    if (ij.second != Max) { ++ij.second; return *this; }
    if (ij.first != Max) { ij.second = 0; ++ij.first; return *this; }

    done = true;
    return *this;
  }

  PairSequence operator++(int) {
    PairSequence const tmp(*this);
    ++*this;
    return tmp;
  }

private:
  bool done;
  value_type ij;
};

所以嗯,可能C++的表达方式稍微多一些文字 :)

撰写回答