在这种情况下,(N)RVO是否可以与我的功能一起使用?

时间:2019-01-23 18:40:52

标签: c++ rvo nrvo

我有以下代码:(好吧,实际上它要复杂得多,但为了简化起见我对其进行了简化。因此,请忽略那些看起来很愚蠢的事情。在我的实际情况下我无法更改它们)

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

2 个答案:

答案 0 :(得分: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);
}

此重写的技巧是省略允许跳过复制/移动ctor。因此,每次我们实际复制一个对象(即使是内联的)时,我们都会插入一个exit子句;唯有省略才能避免。

GenerateReport除无可能的情况外,没有(N)RVO或任何形式的省略。我怀疑编译器是否能够证明这一点,特别是如果字符串是非静态的并且足够大以至于需要堆存储。

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

MSVC fails-通知对 ??0string@@QAE@$QAU0@@Z,这是我的本地string类的move构造函数。

当我强制可能的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编译器可以使用其返回值的函数(嗯,它们不支持lambda,但您知道了)。

答案 1 :(得分:1)

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