C++ return 值和移动规则异常

C++ return value and move rule exceptions

当我们 return 来自 C++ 函数的值复制初始化发生。例如:

std::string hello() {
    std::string x = "Hello world";
    return x; // copy-init
}

假设RVO被禁用。

根据复制初始化规则,如果 x 是非 POD class 类型,则应调用复制构造函数。但是对于 C++11 以后的版本,我看到调用了 move-constrtuctor。我无法找到或理解有关此 https://en.cppreference.com/w/cpp/language/copy_initialization 的规则。所以我的第一个问题是 -

  1. 当值从函数 returned 时,C++ 标准对复制初始化发生的移动有何规定?

  2. 作为上述问题的延伸,我也想知道什么情况下不会发生move。我想到了以下调用复制构造函数而不是移动的情况:

std::string hello2(std::string& param) {
    return param;
}

最后,在一些库代码中我看到 std::move 在 returning 时被显式使用(即使应该发生 RVO 或移动)。例如:

std::string hello3() {
    std::string x = "Hello world";
    return std::move(x);
}
  1. 在 returning 时显式使用 std::move 的优点和缺点是什么?

通过移动构造函数进行初始化是 "copy initialization" 的特例,并且不是作为单独的概念出现,这让您感到困惑。检查 cppreference 页面上的注释。

If other is an rvalue expression, move constructor will be selected by overload resolution and called during copy-initialization. There is no such term as move-initialization.

对于 return 从函数中获取值,检查 the description of returning on cppreference。它在一个名为 "automatic move from local variables and parameters" 的框中说,其中 expression 指的是您 return 的内容(警告:引用被缩短了!阅读原文以获取有关其他情况的完整详细信息):

If expression is a (possibly parenthesized) id-expression that names a variable whose type is [...] a non-volatile object type [...] and that variable is declared [...] in the body or as a parameter of the [...] function, then overload resolution to select the constructor to use for initialization of the returned value is performed twice: first as if expression were an rvalue expression (thus it may select the move constructor), and if the first overload resolution failed [...] then overload resolution is performed as usual, with expression considered as an lvalue (so it may select the copy constructor).

所以在 returning 一个局部变量的特殊情况下,该变量可以被视为右值,即使正常的句法规则会使它成为左值。该规则的精神是,在 return 之后,您无法在 returned 值的复制初始化期间发现局部变量的值是否已被销毁,因此移动它确实不造成任何伤害。

关于你的第二个问题:It is considered bad style to use std::move while returning, because moving will happen anyway, and it inhibits NRVO

引用上面链接的 C++ 核心指南:

Never write return move(local_variable);, because the language already knows the variable is a move candidate. Writing move in this code won’t help, and can actually be detrimental because on some compilers it interferes with RVO (the return value optimization) by creating an additional reference alias to the local variable.

因此您引用的库代码不是最优的。

此外,您不能隐式地从非函数本地的任何内容(即局部变量和值参数)移动,因为隐式移动可能会从函数后仍然可见的内容移动return编辑。在 cppreference 的引用中,重要的一点是 "a non-volatile object type"。当你 return std::string& param 时,这是一个 reference 类型的变量。