Python 的 yield 在 C++ 上的一种实现

来源:互联网 发布:中国移动积分兑换软件 编辑:程序博客网 时间:2024/06/11 04:44

Python 上的 yield 是很令人着迷的语句,其简洁强大充分体现了面向对象的优点。Python 把带有 yield 的函数认为是 “产生生成器的函数”。在我看来,生成器的意义就是,隔离开了值的 “产生” 与 “消耗” 的代码。

很多人试图在 C++ 中实现 yield 语义,比如 boost 就有一种实现。这些实现大抵都是,利用 C++ 的 switch 语句,以 __LINE__ 宏为标签,实现跳转的效果——但我觉得,这背离了生成器原本的意义。

我也实现了 yield 语义,并且也把带有 yield 语义的对象定义为生成器。代码详见https://github.com/JazzLeee/Generator-in-C-

这里演示一下生成器用法,因为程序用到了 C++1y,并且该标准在不同编译器上的实现不一致,我是用 vs2015 编译通过的。

作为例子, 我用生成器实现了一个 Unicode 的解码/编码工具,包含在 acgt/acgt_g_unicode.h 中,下面的代码描述了,如何打开用 utf8/utf16le/utf16be/utf32le/utf32be 编码的文本 unicode.txt:

#include <iostream>#include <acgt/acgt_g_unicode.h>using namespace acgt;int main() {    std::wcout.imbue(std::locale("chs"));    // 函数 utfText 返回一个生成器    // 可用 operator| 传给该生成器 函数/仿函数/匿名函数    utfText("unicode.txt") | [](wchar_t c) {        std::wcout << c;    };    return 0;}

或者说,你想打开 unicode.txt 之后,用 utf8 格式保存,可以这样做

#include <fstream>#include <acgt/acgt_g_unicode.h>using namespace acgt;int main() {    std::ofstream file("utf8.txt", std::ios_base::binary);    // wgener 用于那些编写为“接受一个生成器作为参数”的生成器    // utfText("unicode.txt") 作为生成器传递给了    // wgener<WG_ucs4_to_utf8_with_BOM> 这个生成器    wgener<WG_ucs4_to_utf8_with_BOM>(utfText("unicode.txt")) | [&](char c) {        file.write(&c, 1);    };    return 0;}

下面,演示如何编写一个质数生成器

#include <iostream>#include <cmath>// 生成器模板位于 acgt/acgt_generator.h 中#include <acgt/acgt_generator.h>using namespace acgt;// 首先,写一个判断质数的谓词bool is_prime(unsigned n) {    for (unsigned i = 2; i <= std::sqrt(n); i++)        if (n % i == 0)            return false;    return true;}// 然后,编写一个质数生成器// 任何生成器都是一个 只有一个模板参数T 的模板// 继承于 Generator<T, 参数类型列表>template <    typename T> struct G_Prime : Generator<T, unsigned> {    // 需要用 GInit 来继承父类的构造函数    GInit;    // 然后编写生成器本体,接受参数 n,生成不大于 n 的质数    // 生成器本体是一个 void body(形参列表) const 函数    // 因为需要覆盖父类的函数,所以推荐用 override 检查    void body(        unsigned n    ) const override {        for (unsigned i = 2; i <= n; i++)            if (is_prime(i))                yield(i);    }};int main() {    // 使用 gener 函数来驱动一个生成器工作    gener<G_Prime>(100) | [](unsigned n) {        // 打印质数        std::cout << n << ' ';    };    std::cout << std::endl;    return 0;}

这样的生成器才真真正正起到了隔离值的 生成消耗,实现了更好的面向对象。

我还提供了一些操作,让传给生成器的函数,能够初步控制生成器的行为。
比方说,你可以用 GExit 指令让生成器无条件退出,也可以用 GReset 指令让生成器无条件重启,并且在嵌套生成器中也不会出现问题。

    //......    utfText("unicode.txt") | [](wchar_t c) {        if (c == L'#')            GExit;        std::wcout << c;    };    //......

暂时先写到这里,对生成器有兴趣的朋友可以添加联系。

QQ: 390855044

0 0