【问题标题】:C++ return value optimization, multiple unnamed returnsC++返回值优化,多个未命名返回
【发布时间】:2016-07-11 15:56:31
【问题描述】:

让我们考虑这两个函数:

// 1. Multiple returns of the same named object
string f() {
    string s;
    if (something())
        return s.assign(get_value1());
    else
        return s.assign(get_value2());
}

// 2. Multiple returns, all of unnamed objects
string g() {
    if (something())
        return get_value1();
    else
        return get_value2();
}

这些函数中的每一个在 RVO 中的实际行为方式当然取决于编译器。但是,我是否可以假设他们两个的 RVO 都很常见?


p.s.(见答案)功能 #1 旨在如下:

string f() {
    string s;
    if (something())
        return s;
    s.assign(get_value());
    return s;
}

【问题讨论】:

    标签: c++ rvo


    【解决方案1】:

    对于#1,NRVO 保证不会发生,也就是说,保证您从s 获取到函数返回值的副本。在这种情况下,你最好这样做

    return std::move(s.assign(get_value1()));
    

    或者,如果可能,将函数重写为对 NRVO 友好:

    string f() {
        string s;
        if (something())
            s.assign(get_value1());
        else
            s.assign(get_value2());
        return s;
    }
    

    在编译器考虑 NRVO 之前,必须满足几个标准要求。这里不满足的是return 语句中的表达式必须是变量的名称。 s.assign(...) 不是一个名字,它是一个更复杂的表达式;您需要有类似 return s; 的信息才能考虑 NRVO。

    对于#2,假设get_value 函数返回string(或const string),您很可能在任何现代编译器上都有RVO,并且如果C++17 的批准一切顺利,在任何符合标准的编译器中,RVO 将在 C++17 模式下保证(仍然不能保证 NRVO)。

    您可以在cppreference.com 上找到有关 (N)RVO(在标准中称为复制省略)的非常好的和全面的信息。


    我决定检查当前的编译器状态,所以我在 GCC 6.1.0、Clang 3.8.0 和 MSVC 2015 Update 3 上做了一些测试。

    对于 #2,您确实从所有三个编译器中获得了 RVO(return 语句中的纯右值很容易分析)。

    您还可以从所有三个编译器中获得 NRVO,以获得类似于上述“NRVO 友好”的构造(对于 MSVC,您需要启用优化)。

    但是,对于像这样的功能

    string f() {
        string s;
        if (something())
            return s;
        s.assign(get_value());
        return s;
    }
    

    GCC 和 Clang 执行 NRVO,但 MSVC 不执行;但是,它确实会生成从 s 到返回值的移动,这是符合标准的。

    再举个例子:

    string f() {
        string s;
        if (something())
            return get_value1();
        if (something_else())
            return get_value2();
        s.assign(get_value3());
        return s;
    }
    

    所有三个编译器都对前两个 returns 执行 RVO,并从 s 移动第三个。

    【讨论】:

    • 感谢您的回复。我确实认为get_value 评估为string。而且我在压缩示例时被过度使用,我对#1 的意思实际上更像string s; if(sthg()) {return s;} s.assign(get_value()); return s;。仍然提醒我,如果我粗心,“复制省略”将不会发生。由于我的问题主要集中在第二种情况,我将其标记为已解决,不会进一步编辑。
    • @n.caillou 该构造确实勾选了复制省略的标准要求的所有框,看起来您确实从 GCC 和 Clang 获得 NRVO,但不是从 MSVC(2015 更新 3)获得 - 多个具有相同变量名的 return 语句仍然会混淆它。然而,它确实会生成从s 到返回值的移动,这是符合标准的。如果 MSVC 在您的情况下很重要,最好重写函数以在一个位置返回 return s;。多个return get_value(); 对所有三个编译器都很好 - prvalues 更容易让编译器分析。
    • 再想一想,我想我会把这条评论移到答案中。
    • 我想知道最好的方法是什么,如果我想返回一个包含另一个容器的对会发生什么,比如std::pair<std::vector<int>, int> foo() { std::vector<int> vec = (/*something useful*/); return std::make_pair(vec, 42); }?如果我理解正确,std::pair 可能会发生复制省略,因为 return 的语句是无名的临时语句,但是向量会发生什么?
    • @yussuf 会从vec复制到make_pair返回的pair的第一个成员,这样return std::make_pair(std::move(vec), 42);会更好。对于vectors,移动相对便宜,因此在实践中,可能不值得再麻烦了。如果您可以更改函数的结构并且您绝对希望避免任何复制或移动(也许您有一个std::array 而不是vector),那么您可以构造一个带有容器的本地pair 变量,对容器成员进行处理,并返回命名的对变量。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多