【问题标题】:Which type to declare to avoid copy and make sure returned value is moved声明哪种类型以避免复制并确保移动返回值
【发布时间】:2013-08-26 13:59:00
【问题描述】:

假设

  • 我有一个函数A f()
  • 我想将一个局部变量a初始化为f的返回值;
  • 我不想依赖 RVO;

避免f的返回值在何时被复制的最佳选择是什么(以及为什么)

  1. a 可能需要修改
  2. 我知道a不会被修改

选项:

a) A a = f(); b)A&& a = f(); c)const A& = f(); d)const A&& = f();


编辑:

我会说:

  1. b)
  2. c)

因为既使用引用又避免了额外的副本(RVO 也可以避免,但不能保证)。那我怎么会看到选项 a) 建议的大部分时间?

我想问题的核心是:我知道 a) 很可能与 c) 具有相同的效果,那么为什么不使用 c) 而不是 a),以使事情明确且独立于编译器?

【问题讨论】:

    标签: c++11 reference return local rvalue-reference


    【解决方案1】:

    那么我为什么大部分时间都看到选项 a) 建议?

    因为所有 4 个选项都以完全相同的方式返回值。唯一改变的是您将变量绑定到返回的临时变量的方式。

    • a) 声明A 类型的变量a 并从临时变量中移动初始化它。这不是赋值,这是初始化。任何流行的编译器都会忽略移动构造函数,前提是您没有明确禁止它,这意味着程序只会确保为返回值保留的存储空间是 a 变量的存储空间。
    • c) 声明一个变量a,它是对临时变量的常量引用,从而延长临时变量的生命周期。这意味着临时返回值得到存储,变量a 将引用它。静态知道引用指向返回值的编译器将有效地生成与 a) 相同的代码。
    • b) 和 d),我不太确定它们有什么用处。 (即使可行)此时我不会使用右值引用。如果需要,我稍后会明确地std::move 变量。

    现在:

    可能需要修改

    如果a 可以修改,请使用:

    auto a = f();
    

    我知道a不会被修改

    如果您知道a 不会被修改,请使用:

    const auto a = f();
    

    使用auto 将防止任何类型不匹配。 (因此,如果 f 毕竟不返回类型 A,则任何隐式转换为 A。哦,那些维护者......)

    【讨论】:

    • d) 是有效的,但实际上它与 c) 相同(因此不是很有用)。 b) 类似,但值可能会改变。我看不出它为什么会出错:“f()”的结果是一个右值,我只是在引用它。另外,我缺少的一点是:如果 a) 与 c) 具有相同的效果,为什么不使用 c) 而不是 a),使事情变得明确而不依赖于编译器?
    • a) 肯定会创建一个局部变量。如果你的 f() 函数由于某种原因开始返回一个引用,c) 会将a 绑定到一个可能不属于你的函数的对象。 (并且您的调用函数可能会开始产生副作用)如果您想要一个局部变量,请将其声明为这样。如果你想要一个引用,声明一个引用。
    • 如果f开始返回一个在f之外无效的引用,问题出在f,而不是f的客户端
    • 我不是这个意思。如果f 突然改变签名并返回一个引用,你希望a 是什么?引用与返回的引用引用相同的对象?还是对象的副本?我的意思是,如果您的意思是 a 应该是一个局部变量,请使用 autoconst auto 声明它。如果您的意思是 a 应该引用 f() 返回的任何内容,请将其声明为引用。
    • 不过,如果返回的引用是const,使用应该没有问题。如果不是并且不应该更改,那么我想错误的选择仍然在f。如果f 返回一个引用,它基本上是在说“随心所欲”。如果确实如此,那么我也不需要副本。
    【解决方案2】:

    如果class A 有移动构造函数,那么只需使用A a = f(); 如果您对class A 一无所知,则只能依靠 RVO 或 NRVO 优化。

    【讨论】:

    • 假设我对A一无所知;为什么不A&& a = f()const A& a = f() (以确保我不会制作额外的不必要的副本,即使没有发生*RVO)?
    • 如果你想避免依赖复制/移动省略,你必须避开任何复制/移动语义,而且只有一种方法。将f 声明为void f(A& ret) 并调用A a; f(a);
    • @ricab:我猜编译器实现复制省略是一个合理的期望。容器被移动,而不是被复制。如果 A 很重,您不认为从f 返回您的对象涉及另一个副本吗? f 无法猜测它的返回值将绑定到一个引用。
    • A a = f(); 中没有赋值,这称为复制初始化(标准 8.5.14),它可能会调用移动。
    • @ricab 实际上,这就是 c) 所阻止的。如果将返回值绑定到A& aconst A& a,那么当a 被破坏时,它会引用B 类型的对象并调用B 类的析构函数。但是您的代码将其视为对A 的引用。
    猜你喜欢
    • 2019-08-01
    • 2023-03-08
    • 2021-04-16
    • 2014-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-21
    • 1970-01-01
    相关资源
    最近更新 更多