以下是 C++ 标准允许编译器重建代码的示例。我正在使用完整的 NRVO。请注意放置的使用new
,这是一个比较晦涩的 C++ 功能。您传递new
一个指针,它在那里而不是在免费存储中构造结果。
#include <iostream>
void __foo(void* __construct_std_string_at)
{
new(__construct_std_string_at)std::string("abc");
}
void __goo(void* __construct_std_string_at)
{
__foo(__construct_std_string_at);
}
int main()
{
unsigned char __buff[sizeof(std::string)];
// Is a temporary allocated on the heap to support this, even for a moment?
__goo(&__buff[0]);
const std::string & b = *reinterpret_cast<std::string*>(&__buff[0]);
// ... more code here using b I assume
// end of scope destructor:
reinterpret_cast<std::string*>(&__buff[0])->~std::string();
}
如果我们阻止了 NRVO goo
,它看起来会像
#include <iostream>
void __foo(void* __construct_std_string_at)
{
new(__construct_std_string_at)std::string("abc");
}
void __goo(void* __construct_std_string_at)
{
unsigned char __buff[sizeof(std::string)];
__foo(&__buff[0]);
std::string & a = *reinterpret_cast<std::string*>(&__buff[0]);
new(__construct_std_string_at)std::string(a);
// end of scope destructor:
reinterpret_cast<std::string*>(&__buff[0])->~std::string();
}
int main()
{
unsigned char __buff[sizeof(std::string)];
// Is a temporary allocated on the heap to support this, even for a moment?
__goo(&__buff[0]);
const std::string & b = *reinterpret_cast<std::string*>(&__buff[0]);
// ... more code here using b I assume
// end of scope destructor:
reinterpret_cast<std::string*>(&__buff[0])->~std::string();
}
基本上,编译器知道引用的生命周期。因此它可以创建“匿名变量”来存储变量的实际实例,然后创建对它的引用。
我还注意到,当你调用一个函数时,你实际上(隐式地)传递了一个指向返回值所在的缓冲区的指针。因此,被调用函数在调用者的范围内“就地”构造对象。
使用NRVO,被调用函数范围内的命名变量实际上是在调用函数“返回值所在的地方”构造的,这使得返回变得容易。没有它,您必须在本地完成所有操作,然后在 return 语句中将您的返回值复制到通过相当于放置 new 的隐式指针指向您的返回值。
不需要在堆上做任何事情(又名免费存储),因为生命周期都很容易证明和堆栈排序。
原始的foo
和goo
具有预期签名的必须仍然存在,因为它们具有外部链接,直到发现没有人使用它们时可能被丢弃。
所有以 开头的变量和函数__
仅用于说明。编译器/执行环境不再需要命名变量,就像您需要为红细胞命名一样。(理论上,因为__
是保留的,所以在编译之前进行这种翻译传递的编译器可能是合法的,如果您实际使用了这些变量名并且它未能编译,那将是您的错,而不是编译器的错,而是......那将是一个非常老套的编译器。;))