C++11编译器何时会使RVO和NRVO优于移动语义和常量引用绑定



考虑从函数返回启用了移动语义的"完整"对象的情况,如std::basic_string<>:

std::wstring build_report() const
{
    std::wstring report;
    ...
    return report;
}

那么,是否可以像中那样,现实地期望我做出"最佳"选择,是否使用带有移动语义的返回字符串

const std::wstring report(std::move(build_report()));

或者我是否应该依靠(N)RVO来进行

const std::wstring report(build_report());

甚至用将const引用绑定到临时

const std::wstring& report(build_report());

有什么方案可以对这些选项进行确定性选择(如果有的话)

EDIT1:请注意,上面std::wstring的用法只是启用移动语义的类型的一个示例。它还不如换成你的arbitrary_large_structure。:-)

第2版:我在VS 2010中运行以下版本的速度优化版本时检查了生成的程序集:

std::wstring build_report(const std::wstring& title, const std::wstring& content)
{
    std::wstring report;
    report.append(title);
    report.append(content);
    return report;
}
const std::wstring title1(L"title1");
const std::wstring content1(L"content1");
const std::wstring title2(L"title2");
const std::wstring content2(L"content2");
const std::wstring title3(L"title3");
const std::wstring content3(L"content3");
int _tmain(int argc, _TCHAR* argv[])
{
    const std::wstring  report1(std::move(build_report(title1, content1)));
    const std::wstring  report2(build_report(title2, content2));
    const std::wstring& report3(build_report(title3, content3));
    ...
    return 0;
}

两个最有趣的结果:

  • report1显式调用std::move以使用move构造函数使指令计数增加三倍
  • 正如James McNellis在下面的回答中所指出的,report2report3确实生成了相同的程序集,其指令比显式调用std::move少3倍

std::move(build_report())是完全不必要的:build_report()已经是一个右值表达式(它是对按值返回对象的函数的调用),因此如果std::wstring有一个移动构造函数,就会使用它(它确实有)。

此外,当您返回一个局部变量时,如果它是具有move构造函数的类型,则它会被移动,因此不会进行复制,句号。

report声明为对象或常量引用之间不应该有任何函数差异;在这两种情况下,最终都会得到一个对象(命名的report对象或可以绑定report引用的未命名对象)。

我不确定这是否是标准化的(正如Nicol所说,所有优化都取决于编译器),但我听到STL谈论过这一点,并且(至少在MSVC中),RVO发生在之前。因此,如果有机会申请RVO,那么你就不会采取任何行动。其次,当您返回一个临时值时,您不必编写std::move(我认为这个实际上在标准中是),因为返回值将被隐式地视为右值。

结果是:不要对编译器进行事后猜测,只需编写看起来最自然的代码,它就会给你最好的结果。

有什么方案可以对这些选项进行确定性选择(如果有的话)?

没有,也永远不会有。

编译器执行任何类型的优化都不需要。你唯一能确定的就是编译一些代码,看看另一端会发生什么。

你最终会得到的最多的是一个通用的启发式,这是一个社区共识,人们会说,"对于大多数编译器来说,X似乎工作得最快。"但仅此而已。随着编译器跟上C++0x的速度和实现的成熟,这将需要的时间。

最新更新