0

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

#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。

4

2 回答 2

1

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

#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);
}

这种重写的技巧是省略允许跳过复制/移动ctors。所以每次我们实际复制一个对象(即使是内联的),我们都会插入一个exit子句;只有通过省略,我们才能避免它。

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

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

MSVC 失败——注意对 的调用 ??0string@@QAE@$QAU0@@Z,这是我本地string类的移动构造函数。

当我通过说它为空来强制运行可能的 RVO 案例时,您会看到编译器也无法在此处进行 RVO 优化;有一个exit(-1)内联到反汇编。

Clang设法 RVOreturn 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 编译器可以省略返回值的函数(好吧,它们不支持 lambda,但你明白了)。

于 2019-01-23T19:02:37.043 回答
1

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

于 2019-01-23T19:05:15.070 回答