【问题标题】:C++ return value and move rule exceptionsC++ 返回值和移动规则异常
【发布时间】:2020-09-17 17:39:06
【问题描述】:

当我们从 C++ 函数返回一个值时,复制初始化就会发生。例如:

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

假设 RVO 已禁用。

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

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

  2. 作为上述问题的扩展,我还想知道在什么情况下不会发生移动。我想出了以下情况,其中调用了复制构造函数而不是移动:

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

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

std::string hello3() {
    std::string x = "Hello world";
    return std::move(x);
}
  1. 返回时显式使用std::move有什么好处和坏处?

【问题讨论】:

标签: c++11 initialization move copy-constructor rvo


【解决方案1】:

您对通过移动构造函数进行初始化是“复制初始化”的一种特殊情况这一事实感到困惑,并且不是单独的概念。检查 cppreference 页面上的注释。

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

要从函数返回值,请检查the description of returning on cppreference。它在一个名为“从局部变量和参数自动移动”的框中说,其中 expression 指的是您返回的内容(警告:该报价已缩短!阅读原文以获取有关其他情况的完整详细信息):

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

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

关于你的第二个问题:It is considered bad style to use std::move while returning,因为无论如何都会发生移动,and it inhibits NRVO

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

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

所以你引用的库代码不是最理想的。

此外,您不能隐式地从非函数本地的任何东西(即局部变量和值参数)移动,因为隐式移动可能会从函数返回后仍然可见的东西移动。在 cppreference 的引用中,重点是“非易失性 object 类型”。当你返回std::string& param时,这是一个reference类型的变量。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-03
    • 2011-06-11
    • 1970-01-01
    • 2018-03-29
    • 1970-01-01
    • 1970-01-01
    • 2011-10-19
    • 1970-01-01
    相关资源
    最近更新 更多