接受按值或右值引用的仅移动参数

Accept move-only parameter by value or rvalue reference

本文关键字:移动 参数 引用      更新时间:2023-10-16

这篇文章的公认答案是按值传递与按右值引用传递:

对于仅移动类型(如std::unique_ptr),按值传递似乎是规范…

我对此有点怀疑。假设有一些不可复制的类型,Foo,移动起来也不便宜;和有成员FooBar类型

class Foo {
public:
    Foo(const Foo&) = delete;
    Foo(Foo&&) { /* quite some work */ }
    ...
};
class Bar {
public:
    Bar(Foo f) : f_(std::move(f)) {}    // (1)
    Bar(Foo&& f) : f_(std::move(f)) {}  // (2)
    // Assuming only one of (1) and (2) exists at a time
private:
    Foo f_;
};

然后对于下面的代码:

Foo f;
...
Bar bar(std::move(f));

Constructor(1)产生2个move构造,而Constructor(2)只产生1个move构造。我还记得在Scott Meyers的Effective Modern c++ 中读到过这方面的内容,但不能立即记住是哪一项。

所以我的问题是,对于仅移动类型(或者更一般地说,当我们想要转移参数的所有权时),我们不应该更倾向于通过rvalue-reference传递来获得更好的性能吗?

UPDATE:我知道按值传递构造函数/赋值操作符(有时称为统一函数/赋值操作符)可以帮助消除重复代码。我应该说我对以下情况更感兴趣:(1)性能很重要,(2)类型不可复制,因此没有接受const左值引用参数的复制操作符/赋值操作符。

UPDATE 2:所以我找到了Scott Meyers关于具体问题的博客:http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html。本博客讨论了他在Effective Modern c++ 的第41条中所提倡的理由:

考虑只对可复制参数按值传递移动起来很便宜…[and] 总是复制

关于按值传递和右值引用有广泛的讨论,这里引用太多了。关键是,两种方法都有各自的优点和缺点,但对于转移仅移动对象的所有权,通过右值引用传递似乎更可取。

在这种情况下我们可以鱼与熊掌兼得。只对foo类引用启用的模板构造函数为我们提供了完美的转发以及构造函数的单个实现:

#include <iostream>
#include <utility>
class Foo {
public:
    Foo() {}
    Foo(const Foo&) = delete;
    Foo(Foo&&) { /* quite some work */ }
};
class Bar {
public:
  template<class T, std::enable_if_t<std::is_same<std::decay_t<T>, Foo>::value>* = nullptr>
    Bar(T&& f) : f_(std::forward<T>(f)) {}  // (2)
    // Assuming only one of (1) and (2) exists at a time
private:
    Foo f_;
};
int main()
{
  Foo f;
  Bar bar(std::move(f));
  // this won't compile
//  Foo f2;
//  Bar bar2(f2);
}

Background

很难想象一个类的移动代价是昂贵的:move语义恰恰来自于在语义允许的情况下,提供一个快速替代拷贝的需求。

您带来了std::string和SSO的示例。然而,这个例子显然是有缺陷的(我怀疑他们甚至开启了优化),因为通过memcpy复制16个字节应该花费大量的CPU周期,因为它可以在1个SIMD指令中实现一次存储所有字节。而且,msvc10真的很老了。


所以我的问题是,对于只移动类型(或者更一般地说,当我们想要转让所有权的论点),我们不应该更喜欢通过rvalue-reference传递以获得更好的性能?

我不会谈论性能,因为它是一个特殊的方面,不能"一般地"分析。我们需要具体的案例。此外,编译器优化也需要考虑;事实上,相当沉重。而且不要忘记进行彻底的性能分析。

std::unique_ptr有点不同,因为(i)只能由于拥有语义而移动(ii)它移动成本低。

我的看法。我想说,如果你必须提供一个"更快"的替代API,提供两者-就像std::vector::push_back。就像你说的,它可以有一点改进。
否则,即使对于仅移动类型,通过const-reference传递仍然有效,如果您认为它不能,请使用按值传递。