【问题标题】:Implementing a generator in C++0x在 C++0x 中实现生成器
【发布时间】:2026-02-04 13:20:02
【问题描述】:

python 关键字yield 对我来说是一个很好的概念抽象,让我能够将算法的重要部分提炼成人类可读的形式。我们之前讨论过:

Python generators in various languages

在 C++ 中为仅限 Windows 的库给出了答案。此外,我在问题中发现了另一个使用时髦宏扩展的示例:

Generators in C++ — invalid use of nonstatic data member

我的计算机科学知识的边缘告诉我,yield 函数具有 something to do with co-routines 和 monad,但我不太了解它如何适合 C++ 或 C++0x 可以完成的任务。

似乎在 C++ 中,如果不使用宏扩展或仅 Windows 光纤(线程),yeild 就无法实现。这是真的?这个问题会随着 C++0x 的附加语言特性而改变吗?

【问题讨论】:

    标签: c++ c++11 generator


    【解决方案1】:

    您可以将yield python 机制映射到 C++ 迭代器。

    参见Boost Function Input Iterator 和示例:

    函数输入迭代器允许创建迭代器,这些迭代器封装了一个空函数对象和一个状态对象,该对象跟踪迭代器增加的次数。函数输入迭代器对 InputIterator 概念进行建模,可用于创建有界输入迭代器。

    与生成器迭代器一样,函数输入迭代器采用一个模拟生成器概念的函数(基本上是一个空值或 0 元函数对象)。函数函数输入迭代器的每个增量都会调用生成器函数并将值存储在迭代器中。当迭代器被取消引用时,存储的值被返回。

    【讨论】:

    • 这和Python的yield不太一样。
    • @KennyTM:不同的语法,相同的概念。
    • @MaximYegorushkin:users.softlab.ece.ntua.gr/~ttsiod/yield.html 轻松 使用 Boost 函数输入迭代器如何?我的意思很简单,即不需要要求用户知道应该将什么状态保存到生成器仿函数。
    • @KennyTM 我试过并且必须承认在 C++ 中复制 Python 代码并非易事。然而,在实践中,我会使用sgi.com/tech/stl/next_permutation.html
    【解决方案2】:

    yield 基本上是一种实现受限形式协程的方法。

    如果你非常想,你可以(例如,“人们有”)使用setjmplongjmp 在 C 中实现相对完整的协程。在 C++ 中,您可以可能 做同样的事情,尽管我不完全确定。 C++ 的问题成为决定何时执行什么 dtor 的问题之一。顺便说一句,我认为答案是 dtors 不应该受到协程使用的影响,但我并没有真正考虑过。假设这是正确的,那么大致相同的代码应该适用于 C++ 和 C。

    C++0x 添加了对线程等的全面支持。虽然它可能很笨拙和/或增加开销,但几乎所有你希望用纤维做的事情,你也可以用线程做。因此,它会更直接地支持该习语,因此实现起来会容易一些。

    【讨论】:

    • 正如你所说,longjmp 根本不能与 C++ 很好地混合。
    • 什么?为什么 longjump 不能与 C++ 很好地混合(顺便说一句,我没有得到 Jerry 是在说你所说的那样的印象)。
    • 我听说过在 C++ 中使用调用堆栈对象和非常仔细编码的函数来编写协程。
    • 在 Linux 上(可能还有其他 Posix 系统,因为它在 2008 年之前是 Posix 的一部分),我认为 makecontext 和 swapcontext 是正确的函数。
    【解决方案3】:

    嗯,我自己从来没有使用过生成器,但据我了解,您需要一些函数来返回实现增量运算符 ++ 的迭代器。伪代码:

    int f(int x) {
        for(int i = 0; i < x; ++i) {
            yield(i);
        }
    }
    
    for(iterator it = make_generator(std::bind(f, 5));
        it != generator_end();
        ++it)
    {
        do_something(*it);
    }
    
    // iterator specialized for the return type of f
    template<typename T>
    iterator<T> make_generator(Function<T (void)> f) {
        ...
    }
    

    您需要从指向函数f 的指针构造迭代器,并且可以使用std::bind 将任何参数传递给f

    现在在每次迭代中,您需要在某个时刻有效地调用f,并且函数yield 必须进行一些堆栈操作以保存当前堆栈,返回产生的值并在再次调用该函数时恢复堆栈。您只需要将堆栈保存到调用 f 的位置(不需要整个堆栈),但这可能很难实现。您可能必须在调用 f 之前保存堆栈指针,然后在某处复制(前一个堆栈指针..当前堆栈指针)的内容。

    【讨论】: