【问题标题】:Will (N)RVO be applied with my function in this situation?在这种情况下,(N)RVO 会与我的功能一起应用吗?
【发布时间】:2019-01-23 18:40:52
【问题描述】:

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

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

【问题讨论】:

  • 它可能会发生也可能不会发生。确定的唯一方法是检查程序集。
  • 如果您有return report;,那么 NRVO 几乎是肯定的。但是,由于您使用 .c_str() 构造另一个单独的字符串,我认为对于 NRVO 来说这是一个需要考虑的范围。
  • 哪个函数? DoItGenerateReport?
  • 两者。抱歉,我以为我写的是函数,而不是函数。我的错。
  • 请发布实际的minimal reproducible example 而不是伪代码。

标签: c++ rvo nrvo


【解决方案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 下。我怀疑编译器是否能够证明这一点,尤其是当字符串是非静态的并且大到需要堆存储时。

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

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

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

Clang 设法对return string(); 进行 RVO,但不是对return val; 进行 NRVO。

到目前为止,最简单的解决方法是:

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,但你明白了)。

【讨论】:

  • 虽然report 不能进行NRVO,但从它构造的std::string 返回值可以直接RVO 到val。虽然,从 C++17 开始,这不再是优化,而是必需的行为。
  • 据我所知,NRVO 仅适用于 id 表达式。我弄错了吗?
  • @eerorika 对不起,我读到 return val 不是 return report.c_str() 不是 NRVO 的。我的错。
  • 哇,感谢@Yakk-AdamNevraumont 的全面解释
  • @conectionist 建议添加几乎保证省略的解决方案。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-17
    • 2022-07-22
    • 1970-01-01
    • 1970-01-01
    • 2013-05-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多