首先请看下面的代码,它由2个翻译单元组成。
--- foo.h ---
class Foo
{
public:
Foo();
Foo(const Foo& rhs);
void print() const;
private:
std::string str_;
};
Foo getFoo();
--- foo.cpp ---
#include <iostream>
Foo::Foo() : str_("hello")
{
std::cout << "Default Ctor" << std::endl;
}
Foo::Foo(const Foo& rhs) : str_(rhs.str_)
{
std::cout << "Copy Ctor" << std::endl;
}
void Foo:print() const
{
std::cout << "print [" << str_ << "]" << std:endl;
}
Foo getFoo()
{
return Foo(); // Expecting RVO
}
--- main.cpp ---
#include "foo.h"
int main()
{
Foo foo = getFoo();
foo.print();
}
请确保 foo.cpp 和 main.cpp 是不同的翻译单元。因此,根据我的理解,我们可以说在翻译单元 main.o (main.cpp) 中没有可用的 getFoo() 的实现细节。
但是,如果我们编译并执行上面的代码,我看不到指示 RVO 在这里工作的“Copy Ctor”字符串。
即使“getFoo()”的实现细节没有暴露给翻译单元 main.o,如果你们中的任何人告诉我如何实现这一点,我将不胜感激?
我用GCC (g++) 4.4.6进行了上述实验。
最佳答案
编译器只需要始终如一地工作。
换句话说,编译器必须只查看返回类型,并根据该类型决定返回该类型对象的函数将如何返回值。
至少在典型情况下,该决定相当微不足道。它留出一个寄存器(或可能两个)用于返回值(例如,在通常为 EAX 或 RAX 的 Intel/AMD x86/x64 上)。任何小到可以放入其中的类型都将返回到那里。对于任何太大而无法放在那里的类型,该函数将接收一个隐藏的指针/引用参数,告诉它在哪里存放返回结果。请注意,这在根本不涉及 RVO/NRVO 的情况下非常适用——事实上,它同样适用于返回 struct
的 C 代码,就像它适用于返回 class
的 C++目的。虽然返回 struct
在 C 中可能不像在 C++ 中那么常见,但它仍然是允许的,并且编译器必须能够编译执行它的代码。
确实有两个独立的(可能的)拷贝可以被消除。一种是编译器可能会在堆栈上为保存返回值的局部变量分配空间,然后从那里复制到返回期间指针指向的位置。
第二个可能是从该返回地址到值真正需要结束的其他位置的可能拷贝。
第一个在函数本身内部被消除,但对其外部接口(interface)没有影响。它最终将数据放在隐藏指针告诉它的任何地方——唯一的问题是它是先创建一个本地拷贝,还是总是直接与返回点一起工作。显然对于 [N]RVO,它总是直接工作。
第二个可能的拷贝是从那个(潜在的)临时拷贝到值(value)真正需要结束的任何地方。这是通过优化调用序列而不是函数本身来消除的——即,为函数提供一个指向该返回值最终目的地的指针,而不是指向某个临时位置,然后编译器将从该位置将值复制到它的目的地.
关于c++ - g++:在涉及多个翻译单元的情况下RVO如何工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11615231/