c++11 - C++ 返回值和移动规则异常
问题描述
当我们从 C++ 函数返回一个值时,就会发生复制初始化。例如:
std::string hello() {
std::string x = "Hello world";
return x; // copy-init
}
假设 RVO 已禁用。
根据 copy-init 规则,如果x
是非 POD 类类型,则应调用复制构造函数。但是对于 C++11 以后的版本,我看到调用了 move-constructor。我无法找到或理解有关此https://en.cppreference.com/w/cpp/language/copy_initialization的规则。所以我的第一个问题是——
当从函数返回值时,C++ 标准对 copy-init 发生的移动有什么看法?
作为上述问题的延伸,我还想知道在什么情况下不会发生移动。我想出了以下情况,其中调用了复制构造函数而不是移动:
std::string hello2(std::string& param) {
return param;
}
最后,在一些库代码中,我看到std::move
在返回时显式使用了它(即使 RVO 或 move 应该发生)。例如:
std::string hello3() {
std::string x = "Hello world";
return std::move(x);
}
- 返回时显式使用的优缺点是
std::move
什么?
解决方案
您对通过移动构造函数进行初始化是“复制初始化”的一种特殊情况这一事实感到困惑,并且不是单独的概念。检查 cppreference 页面上的注释。
如果 other 是右值表达式,则移动构造函数将由重载决议选择并在复制初始化期间调用。没有移动初始化这样的术语。
要从函数返回值,请查看cppreference 上的返回说明。它在一个名为“从局部变量和参数自动移动”的框中说,其中表达式是指您返回的内容(警告:该报价已缩短!阅读原文以获取有关其他情况的完整详细信息):
如果expression是一个(可能带括号的)id 表达式,它命名一个类型为 [...] 的变量为非易失性对象类型 [...] 并且该变量在正文中声明为 [...] 或作为[...] 函数的参数,然后执行两次重载决议以选择用于初始化返回值的构造函数:第一次好像表达式是一个右值表达式(因此它可以选择移动构造函数),如果第一次重载解析失败 [...] 然后像往常一样执行重载解析,表达式被视为左值(因此它可以选择复制构造函数)。
因此,在返回局部变量的特殊情况下,可以将变量视为 r 值,即使正常的语法规则会将其设为左值。该规则的精神是,返回后,在返回值的复制初始化过程中,您无法查明局部变量的值是否已被破坏,因此移动它不会造成任何损坏。
关于你的第二个问题:返回时使用被认为是不好的风格std::move
,因为无论如何都会发生移动,并且它会抑制 NRVO。
引用上面链接的 C++ 核心指南:
永远不要写
return move(local_variable);
,因为语言已经知道变量是移动候选者。编写move
此代码无济于事,实际上可能是有害的,因为在某些编译器上,它会通过创建局部变量的附加引用别名来干扰 RVO(返回值优化)。
因此,您引用的库代码不是最理想的。
此外,您不能隐式移动非函数本地的任何内容(即局部变量和值参数),因为隐式移动可能会从函数返回后仍然可见的内容移动。在 cppreference 的引用中,重点是“非易失性对象类型”。当您返回时std::string& param
,这是一个具有引用类型的变量。
推荐阅读
- algorithm - 为什么二进制搜索算法适用于这个一维“寻峰”问题?
- arrays - 循环数组:While 循环和 2D 数组
- swift - 对于面临屏幕问题的新 IOS 更新 ios 13.5.1
- python - python中的递归未分配lambda
- android - 如何使用 `cv2.putText` (Android +OpenCV) 在图像上绘制度数符号 (º) 特殊字符?
- datetime - 在 Flutter 中,我得到了一个 DateTime [yyyy-MM-dd 00:00:00.000] 我如何才能将其仅转换为 [yyyy-MM-dd]?
- python - 在“uv”模式下绘制时获取箭袋箭头(尖端和底部)的坐标
- google-apps-script - 有没有办法将 Gmail 附件保存为 MP4?
- flutter - 带有 onPressed 索引的不同页面上的轮播图像
- c - C 编译器使用 Cray 上 Fortran 模块中定义的变量