C++ 返回值和移动规则异常

C++ return value and move rule exceptions

提问人:tinkerbeast 提问时间:5/30/2020 更新时间:6/2/2020 访问量:264

问:

当我们从 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

  1. C++ 标准对从函数返回值时复制初始化发生的移动有什么看法?

  2. 作为上述问题的延伸,我还想知道在什么情况下不会发生移动。我想出了以下情况,其中调用了 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);
}
  1. 退货时显式使用的优缺点是什么?std::move
C++11 初始化 move copy-constructor rvo

评论

0赞 M.M 5/30/2020
查找“NRVO”以了解这种情况
0赞 M.M 5/30/2020
这是否回答了您的问题:stackoverflow.com/questions/12953127/......
0赞 Cubbi 6/2/2020
这里的关键缺失点是 C++ 中 return 语句的语义 11

答:

1赞 Michael Karcher 5/30/2020 #1

您感到困惑的是,通过移动构造函数进行初始化是“复制初始化”的特例,而不是单独的概念。查看 cppreference 页面上的注释。

如果 other 是右值表达式,则 move 构造函数将通过重载解析选择并在复制初始化期间调用。没有移动初始化这样的术语。

要从函数返回值,请检查在 cpppreference 上返回的说明。它在一个名为“自动从局部变量和参数移动”的框中说,其中表达式指的是您返回的内容(警告:该引号已缩短!有关其他情况的完整详细信息,请阅读原文):

如果 expression 是一个(可能带括号的)id-expression,它命名一个变量,其类型是 [...] 非易失性对象类型 [...],并且该变量在正文中声明为 [...] 或作为 [...] 函数的参数,则重载解析以选择用于初始化返回值的构造函数将执行两次:第一次,就好像表达式是右值表达式一样(因此它可以选择移动构造函数), 如果第一个重载解析失败 [...],则重载解析将照常执行,表达式被视为左值(因此它可能会选择复制构造函数)。

因此,在返回局部变量的特殊情况下,该变量可以被视为 r 值,即使正常的语法规则会使其成为 l 值。该规则的精神是,在返回后,在返回值的复制初始化过程中,无法发现局部变量的值是否被破坏,因此移动它不会造成任何损害。

关于你的第二个问题:在返回时使用 std::move 被认为是不好的风格,因为无论如何都会发生移动,并且它会抑制 NRVO。

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

永远不要写 ,因为语言已经知道变量是移动候选变量。编写此代码无济于事,实际上可能是有害的,因为在某些编译器上,它会通过创建局部变量的附加引用别名来干扰 RVO(返回值优化)。return move(local_variable);move

因此,您引用的库代码是次优的。

此外,您不能隐式地从函数的非局部内容(即局部变量和值参数)移动,因为隐式移动可能会从函数返回后仍然可见的内容移动。在 cpppreference 的引用中,重要的一点是“非易失性对象类型”。当你返回时,这是一个具有引用类型的变量。std::string& param