在这种情况下,我的函数会应用 (N)RVO 吗?

Will (N)RVO be applied with my function in this situation?

我有以下代码:(好吧,实际上它要复杂得多,但我简化了它以使其更容易理解。所以请忽略那些看似愚蠢的东西。我无法在我的现​​实中改变它们情况)

#include <string>

using std::string;

ReportManager g_report_generator;

struct ReportManager
{
    // I know, using c_str in this case is stupid. 
    // but just assume that it has to be this way
    string GenerateReport() { string report("test"); return report.c_str(); }
}

string DoIt(bool remove_all)
{
    if(g_report_generator.isEmpty())
        return string();

    string val = g_report_generator.GenerateReport();

    if(remove_all)
        g_report_generator.clear();

    return val;
}

void main()
{
    string s = DoIt(true);
}

我的函数会应用 (N)RVO 吗? 我做了一些研究,看起来是这样,但我不太相信,我想要第二个意见(或更多)。

我正在使用 Visual Studio 2017.

为了解决你的问题,我重写了它。

#include <string>


struct string : std::string {
    using std::string::string;

    string(string&& s) {
        exit(-1);
    }
    string(string const&) {
        exit(-2);
    }

    string() {}
};

struct ReportManager
{
    // I know, using c_str in this case is stupid. 
    // but just assume that it has to be this way
    string GenerateReport()
    {
        string report("test");
        return report.c_str();
    }
    bool isEmpty() const { return true; }
    void clear() const {}
};

ReportManager g_report_generator;

string DoIt(bool remove_all)
{
    if(g_report_generator.isEmpty())
        return string();

    string val = g_report_generator.GenerateReport();

    if(remove_all)
        g_report_generator.clear();

    return val;
}

int main()
{
    string s = DoIt(true);
}

这种重写的诀窍是省略允许跳过 copy/move 构造器。所以每次我们实际复制一个对象时(即使是内联的),我们都会插入一个 exit 子句;只有省略才能避免。

GenerateReport 没有 (N)RVO 或任何类型的省略,除了可能在 as-if 下。我怀疑编译器是否能够证明这一点,特别是如果字符串是非静态的并且大到需要堆存储。

对于DoIt,NRVO 和RVO 都是可能的。省略在那里是合法的,即使有副作用。

MSVC fails -- 通知调用 ??0string@@QAE@$QAU0@@Z,也就是我本地string class.

的移动构造函数

当我将可能的 RVO 情况强制为 运行 by saying it is empty 时,您会看到编译器也无法在此处进行 RVO 优化;有一个 exit(-1) 内联到反汇编中。

Clang 设法 RVO return string(); 但不是 NRVO return val;.

到目前为止最简单的修复是:

string DoIt(bool remove_all)
{
    if(g_report_generator.isEmpty())
        return string();

    return [&]{   
      string val = g_report_generator.GenerateReport();

      if(remove_all)
        g_report_generator.clear();

      return val;
    }();
}

它有两个 RVO,还有一个执行简单 NRVO 的 lambda。对您的代码和 C++98 编译器可以省略 return 值的函数进行零结构更改(好吧,它们不支持 lambda,但您明白了)。

我认为 (N)RVO 在这两个函数中都不可行。 GenerateReport 必须从字符数组构造一个字符串,NRVO 已经没有任何东西了。 DoIt returns 两个不同的值通过它控制路径,这使得也无法执行 NRVO。