接受按值或右值引用的仅移动参数
Accept move-only parameter by value or rvalue reference
这篇文章的公认答案是按值传递与按右值引用传递:
对于仅移动类型(如
std::unique_ptr
),按值传递似乎是规范…
我对此有点怀疑。假设有一些不可复制的类型,Foo
,移动起来也不便宜;和有成员Foo
的Bar
类型
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传递仍然有效,如果您认为它不能,请使用按值传递。
- MSVC将仅移动结构参数解释为指针
- 如果需要转换,我可以在读取参数的同时将其移动到另一个参数吗?
- 使用移动语义:右值引用作为方法参数
- 为什么参数在构造 std::thread 时移动两次
- 移动类的成员作为常量引用参数传递
- 当变量和参数名称匹配时,移动语义构造失败
- 使用 std::move 将参数传递给函数,如果该参数声明为按值传递或使用移动操作数 &&,是否有区别?
- 移动构造函数中的默认参数
- 将参数传递给构造函数和成员函数时移动或复制
- 从函数返回元组时,将复制元组的参数,而不是移动元组参数
- C++具有移动语义的可变参数工厂会导致运行时崩溃
- 覆盖Qt的鼠标按下事件中断移动事件参数
- 在简单地移动参数时使用函数模板参数的优点
- 如果没有带有函数签名的 rvalue 参数,是否会执行 C++ 11 中的移动语义?
- x64 函数调用参数推送/移动顺序 (MSVC)
- 如何创建可变参数模板函数来移动参数值并处理左值和右值
- 在谷歌测试或cppunit下移动参数化测试
- 概念检查函数不适用于仅可移动参数
- std::线程构造函数中的可移动参数(Visual c++ 2012)
- 接受按值或右值引用的仅移动参数