关于编译器和链接器如何处理函数调用者的要求因函数使用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调用程序的运行方式:
>调用get_string()
> get_string结果现在在堆栈上,调用者使用它来构造s
现在还有RVO
>调用get_string()
>当get_string完成时,堆栈上没有结果,get_string构造了s,调用者不需要做任何事情来构造s.
解决方法:
无论如何,调用者都为返回对象分配空间.从调用者的角度来看,函数是否使用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被复制(或移动,在这种情况下)两次:一次从ret到retval缓冲区,一次从retval到s.
现在,应用NRVO,只有get_string的定义会改变:
void get_string(void* retval)
{
std::string& ret = *new(retval) std::string;
// do stuff to ret
}
从来电者的角度来看,一切都没有改变.该函数只是直接初始化它将返回到调用者为返回值分配的空间的对象.现在字符串只移动一次:从retval到s.
现在调用者也可以删除副本,因为不需要分配单独的返回值,然后将其复制到正在初始化的对象中:
char retval[sizeof(std::string)];
get_string(retval);
std::string& s(*(string*)retval);
这样,s由get_string直接初始化,不执行任何副本或移动.