提问人:tinkerbeast 提问时间:5/30/2020 更新时间:6/2/2020 访问量:264
C++ 返回值和移动规则异常
C++ return value and move rule exceptions
问:
当我们从 C++ 函数返回一个值时,就会发生复制初始化。例如:
std::string hello() {
std::string x = "Hello world";
return x; // copy-init
}
假设 RVO 已禁用。
根据 copy-init 规则,如果是非 POD 类类型,则应调用复制构造函数。但是,从C++11开始,我看到移动结构被调用。我找不到或理解有关此 https://en.cppreference.com/w/cpp/language/copy_initialization 的规则。所以我的第一个问题是——x
C++ 标准对从函数返回值时复制初始化发生的移动有什么看法?
作为上述问题的延伸,我还想知道在什么情况下不会发生移动。我想出了以下情况,其中调用了 copy-constructor 而不是 move:
std::string hello2(std::string& param) {
return param;
}
最后,在一些库代码中,我看到在返回时显式使用了它(即使应该发生 RVO 或移动)。例如:std::move
std::string hello3() {
std::string x = "Hello world";
return std::move(x);
}
- 退货时显式使用的优缺点是什么?
std::move
答:
您感到困惑的是,通过移动构造函数进行初始化是“复制初始化”的特例,而不是单独的概念。查看 cppreference 页面上的注释。
如果 other 是右值表达式,则 move 构造函数将通过重载解析选择并在复制初始化期间调用。没有移动初始化这样的术语。
要从函数返回值,请检查在 cpppreference 上返回的说明。它在一个名为“自动从局部变量和参数移动”的框中说,其中表达式指的是您返回的内容(警告:该引号已缩短!有关其他情况的完整详细信息,请阅读原文):
如果 expression 是一个(可能带括号的)id-expression,它命名一个变量,其类型是 [...] 非易失性对象类型 [...],并且该变量在正文中声明为 [...] 或作为 [...] 函数的参数,则重载解析以选择用于初始化返回值的构造函数将执行两次:第一次,就好像表达式是右值表达式一样(因此它可以选择移动构造函数), 如果第一个重载解析失败 [...],则重载解析将照常执行,表达式被视为左值(因此它可能会选择复制构造函数)。
因此,在返回局部变量的特殊情况下,该变量可以被视为 r 值,即使正常的语法规则会使其成为 l 值。该规则的精神是,在返回后,在返回值的复制初始化过程中,无法发现局部变量的值是否被破坏,因此移动它不会造成任何损害。
关于你的第二个问题:在返回时使用 std::move
被认为是不好的风格,因为无论如何都会发生移动,并且它会抑制 NRVO。
引用上面链接的 C++ 核心指南:
永远不要写 ,因为语言已经知道变量是移动候选变量。编写此代码无济于事,实际上可能是有害的,因为在某些编译器上,它会通过创建局部变量的附加引用别名来干扰 RVO(返回值优化)。
return move(local_variable);
move
因此,您引用的库代码是次优的。
此外,您不能隐式地从函数的非局部内容(即局部变量和值参数)移动,因为隐式移动可能会从函数返回后仍然可见的内容移动。在 cpppreference 的引用中,重要的一点是“非易失性对象类型”。当你返回时,这是一个具有引用类型的变量。std::string& param
评论