代码之家  ›  专栏  ›  技术社区  ›  ttsiodras

为什么收益率不加到C++0X?

  •  30
  • ttsiodras  · 技术社区  · 14 年前

    我在许多Python程序中使用了yield,它 真的? 在许多情况下清除代码。我 blogged about it 它是我的网站的热门网页之一。

    C还提供了收益率,它是通过保持调用方的状态来实现的,通过一个自动生成的类来保持状态、函数的局部变量等。

    我现在正在阅读关于C++ 0x及其添加的内容,并且在阅读C++中的LAMBDAS实现时,我发现它也是通过自动生成的类来完成的,它配备有操作程序()存储lambda代码。在我的脑海中形成了一个自然的问题:他们是为兰伯斯做的,为什么他们不把它当作支持“产量”的理由呢?

    当然,他们可以看到共同程序的价值…所以我只能猜测他们认为基于宏的实现(例如 Simon Tatham's )作为适当的替代品。但是,它们并不是因为很多原因:被调用方保持状态、不可重入、基于宏(仅此一点就足够了)等。

    编辑: yield 不依赖垃圾收集、线程或光纤。您可以阅读Simon的文章,以了解我正在谈论的编译器正在进行简单的转换,例如:

    int fibonacci() {
        int a = 0, b = 1;
        while (true) {
            yield a;
            int c = a + b;
            a = b;
            b = c;
        }
    }
    

    进入:

    struct GeneratedFibonacci {
        int state;
        int a, b;
    
        GeneratedFibonacci() : state (0), a (0), b (1) {}
    
        int operator()() {
            switch (state) {
            case 0:
                state = 1;
                while (true) {
                    return a;
    
            case 1:
                    int c = a + b;
                    a = b;
                    b = c;
                }
            }
        }
    }
    

    垃圾收集?没有螺纹?不是纤维吗?不,简单的转换?可以说,是的。

    8 回复  |  直到 9 年前
        1
  •  7
  •   Roger Pate    14 年前

    他们是为兰伯斯做的,为什么他们不把它看作是支持产量的呢?

    检查 papers . 有人提议吗?

    …我只能猜测,他们认为基于宏的实现是一个适当的替代品。

    不一定。我敢肯定他们知道这样的宏解决方案存在,但仅仅替换它们并没有足够的动机让新功能通过。


    尽管在一个新的关键字周围有各种各样的问题,但是可以用新的语法来解决这些问题,例如为lambda所做的以及使用auto作为函数返回类型。

    全新的功能需要强大的驱动程序(即人员)来全面分析和推动功能通过委员会,因为他们总是有很多人对根本性的改变持怀疑态度。因此,即使没有您认为是反对收益结构的强有力的技术原因,仍然可能没有足够的支持。

    但从根本上讲,C++标准库已经接受了不同于迭代器的概念。与python的迭代器相比,后者只需要两个操作:

    1. 下一个()返回下一个项或引发StopIteration(2.6中包含的下一个()内置项,而不是使用方法)
    2. iter(一个iter)返回一个iter(所以你可以在函数中对iterables和iterator进行相同的处理)

    C++的迭代器是成对使用的(必须是同一类型),它被分为类别,它将是语义转换,转变为更适合于一个屈服结构,并且这种转变将不符合概念(这已经被放弃了,但是相对较晚)。例如,请参见 rationale 因为(有理由,如果令人失望的话)拒绝了我关于将基于for循环的范围更改为一种形式的评论,这种形式将使编写这种不同形式的迭代器更加容易。

    具体地澄清我对不同迭代器形式的意思:您生成的代码示例需要 另一个 类型为迭代器类型加上用于获取和维护这些迭代器的关联机制。并不是说它不能被处理,但它并不像你一开始想象的那么简单。真正的复杂性是“简单转换”,考虑到“局部”变量的异常(包括在构造期间),控制生成器内局部作用域中“局部”变量的生存期(大多数变量需要在调用之间保存),等等。

        2
  •  22
  •   Stack Overflow is garbage    14 年前

    我不能说他们为什么不加这样的东西,但在兰伯斯的例子中,他们没有 只是 也添加到语言中。

    他们在Boost中作为一个库实现开始了生命,这证明了

    • 兰姆达斯非常有用:很多人在有空的时候会用到它,而且
    • C++ 03中的一个库实现有许多缺点。

    基于此,委员会决定通过 某种 在C++ 0x中的lambdas,我相信他们最初尝试添加更多的通用语言特性来允许 更好的 比Boost有更多的库实现。

    最后,他们把它作为一个核心语言特性,因为他们别无选择:因为不可能 足够好 库实现。

    新的核心语言功能并不是简单地添加到语言中,因为它们似乎是一个好主意。委员会是 非常 不愿意添加它们,以及有问题的功能 真的? 需要证明自己。必须显示的特征是:

    • 可以在编译器中实现,
    • 去解决一个真正的需求,和
    • 一个库实现还不够好。

    在这种情况下,如果 yield 关键词,我们知道第一点是可以解决的。正如您所展示的,这是一个相当简单的转换,可以通过机械方式完成。

    第二点很棘手。多少钱 需要 因为这个在那里?现有的库实现的使用范围有多广?有多少人为此提出要求,或为此提交了建议?

    最后一点似乎也过去了。至少在C++ 03中,一个库实现存在一些缺陷,正如您指出的那样。 能够 证明核心语言实现的合理性。可以在C++ 0x中实现更好的库实现吗?

    所以我怀疑主要的问题是缺乏兴趣。C++已经是一种巨大的语言,没有人希望它变得更大,除非被添加的特征是 真的? 值得的。我怀疑这还不够有用。

        3
  •  8
  •   DevSolar    14 年前

    添加关键字总是很棘手的,因为它会使以前有效的代码失效。您试图避免在一个代码基础上与C++一样大的语言中使用。

    C++的演化是一个公共过程。如果你觉得 yield 应该在那里,对C++标准委员会提出适当的要求。

    你将直接从做出决定的人那里得到你的答案。

        4
  •  7
  •   Atif    9 年前

    所以看起来它并没有进入C++ 11或C++ 14,而是可能进入C++ 17。看看讲座 C++ Coroutines, a negative overhead abstraction 来自2015年中国人民政治协商会议和该文件 here .

    总而言之,他们正在努力扩展C++函数以产生收益并等待函数的特性。看起来他们在Visual Studio 2015中有一个初始实现,不确定Clang是否有一个实现。而且,他们似乎也可能会遇到一些使用yield和wait作为关键字的问题。

    这个演示很有趣,因为他谈到了它是如何简化网络代码的,在那里您等待数据进入以继续处理序列。令人惊讶的是,使用这些新的协程会比现在更快/更少地生成代码。这是一个很好的演示。

    可以找到C++的可恢复函数方案 here .

        5
  •  2
  •   David Thornley    14 年前

    一般来说,你可以通过 committee papers 但最好是保持跟踪,而不是查找特定问题。

    关于C++委员会有一点要记住的是,它是一个志愿委员会,并不能完成它想要的一切。例如,在原始标准中没有散列类型映射,因为它们无法及时生成散列类型映射。可能是委员会里没人在乎 yield 以及如何确保工作完成。

    最好的办法是请一个活跃的委员会成员来调查。

        6
  •  2
  •   Potatoswatter    14 年前

    嗯,对于这样一个微不足道的例子,我看到的唯一问题是 std::type_info::hash_code() 未指定 constexpr . 我相信一致的实现仍然可以做到这一点并支持这一点。无论如何,真正的问题是获取唯一的标识符,所以可能还有另一个解决方案。(显然我借用了你的“主开关”结构,谢谢。)

    #define YIELD(X) do { \
        constexpr size_t local_state = typeid([](){}).hash_code(); \
        return (X); state = local_state; case local_state: ; } \
    while (0)
    

    用途:

    struct GeneratedFibonacci {
        size_t state;
        int a, b;
    
        GeneratedFibonacci() : state (0), a (0), b (1) {}
    
        int operator()() {
            switch (state) {
            case 0:
                while (true) {
                    YIELD( a );
                    int c = a + b;
                    a = b;
                    b = c;
                }
            }
        }
    }
    

    嗯,他们还需要保证哈希值不是0。也没什么大不了的。和A DONE 宏易于实现。


    真正的问题是当您从带有本地对象的作用域返回时会发生什么。没有希望用基于C的语言保存堆栈帧。解决方案是使用一个真正的协同程序,C++0X直接用线程和期货来解决这个问题。

    考虑这个生成器/协同程序:

    void ReadWords() {
        ifstream f( "input.txt" );
    
        while ( f ) {
            string s;
            f >> s;
            yield s;
        }
    }
    

    如果使用类似的技巧 yield , f 一开始就被摧毁了 产量 在循环之后继续循环是非法的,因为你不能 goto switch 通过非pod对象定义。

        7
  •  1
  •   lurscher    14 年前

    作为用户空间库,协程有几个实现。然而,这里是交易,这些实现依赖于非标准的细节。例如,在C++标准上没有指定堆栈帧是如何保存的。大多数实现只是复制堆栈,因为这是大多数C++实现的工作方式。

    对于标准,C++可以通过改进堆栈帧的规范来帮助协同过程。

    事实上,在我看来,将它添加到语言中并不是一个好主意,因为对于大多数完全依赖于编译器的情况,这会使您坚持使用“足够好”的实现。对于使用协程很重要的情况,无论如何这是不可接受的。

        8
  •  0
  •   kelviN    11 年前

    首先同意@potatoswatter。

    支持协程与支持lambdas不同,而不是 简单变换 就像用达夫的设备玩一样。

    你需要 full asymmetric coroutines (堆叠)像Python中的生成器一样工作。实施 Simon Tatham's Chris' 都是无堆叠的 Boost.Coroutine 虽然很重,但还是堆满了。

    不幸的是,C++ 11仍然没有。 yield 对于协同程序,也许C++1Y;

    PS:如果您真的喜欢Python风格的生成器,请看一下 this .