9

我对编译器和链接器如何处理函数调用者的要求根据函数是使用 RVO 还是 NRVO 而不同这一事实感到困惑。

这可能是我的误解,但我的假设是通常没有 RVO 或 NRVO

std::string s = get_string();

如果 get_string 不执行 N?RVO 但如果 get_string 执行 N?RVO 调用代码不执行任何操作s并由函数 get_string 就地构造,则涉及从 get_string 的结果移动构造 s。

编辑:如果没有 N?RVO,这就是我想象的 get_string 调用者的操作方式:

  1. 调用 get_string()
  2. get_string 结果现在在堆栈上,调用者使用它来构造 s

现在有了 RVO

  1. 调用 get_string()
  2. 当 get_string 完成后,栈上没有结果,get_string 构造了 s,调用者不需要做任何事情来构造 s。
4

1 回答 1

13

无论如何,调用者都会为返回对象分配空间。从调用者的角度来看,函数是否使用 RVO 并不重要。

您还混淆了两个单独的复制省略。有 RVO,它忽略了从函数局部变量到返回值的副本,还有另一个从函数返回值到正在初始化的对象的副本,它也经常被忽略。

基本上,没有任何省略,您可以认为来自 OP 的调用看起来像这样(忽略任何别名问题,这实际上都将直接在汇编中实现):

void get_string(void* retval)
{
    std::string ret;
    // do stuff to ret
    new(retval) std::string(std::move(ret));
}

char retval[sizeof(std::string)];
get_string(retval);
std::string s(std::move(*(string*)retval));

字符串ret被复制(或移动,在这种情况下)两次:一次 fromretretval缓冲区,一次从retvalto s

现在,应用 NRVO 后,只有 的定义get_string会改变:

void get_string(void* retval)
{
    std::string& ret = *new(retval) std::string;
    // do stuff to ret
}

从调用者的角度来看,没有任何改变。该函数只是直接将要返回的对象初始化到调用者为返回值分配的空间中。现在字符串只移动一次:从retvals

现在调用者也可以省略一个副本,因为不需要分配一个单独的返回值,然后将其复制到正在初始化的对象中:

char retval[sizeof(std::string)];
get_string(retval);
std::string& s(*(string*)retval);

这样,s直接由 初始化get_string,不进行复制或移动。

于 2018-02-23T20:17:08.850 回答