什么时候需要参考 R 值?
Posted
技术标签:
【中文标题】什么时候需要参考 R 值?【英文标题】:When are R-value references necessary? 【发布时间】:2012-06-23 07:08:21 【问题描述】:我知道,如果没有 R 值引用,C++ 中的完美转发是不可能的。
但是,我想知道:还有什么其他需要他们?
例如,this page 指出了这个例子,其中 R 值引用显然是必要的:
X foo(); X x; // perhaps use x in various ways x = foo();
上面的最后一行:
销毁x
持有的资源, 从foo
返回的临时资源中克隆资源, 销毁临时对象,从而释放其资源。
但是,在我看来,如果swap
正确实施,一个简单的更改就可以解决问题:
X foo();
X x;
// perhaps use x in various ways
X y = foo();
swap(x, y);
所以在我看来,对于这种优化来说,右值引用不是必要的。 (对吗?)
那么,有哪些问题不能用右值引用解决(完美转发除外,我已经知道了)?
【问题讨论】:
移动语义?实际上,完美转发本身不需要右值引用,它只需要不同的类型推导规则。他们对这些新规则使用右值引用,因为它很方便。 @R.MartinhoFernandes:“移动语义”到底是什么意思?是不是我上面展示的东西? (另外,我的意思是,完美的转发是不可能的 交换不能替代X x = std::move(y);
。
@R.MartinhoFernandes:嗯...但是您的示例到底要解决什么问题? (另外,在你的例子中y
是什么?)
y
是一些 X
左值。我正在尝试解决与您相同的问题,但是对于没有默认构造函数的类。另请考虑:struct foo non_default_constructible x; foo(non_default_constructible x) : x(std::move(x)) ;
。你不能在那里交换。交换不能替代任何类型的移动初始化。
【参考方案1】:
那么,有哪些问题是右值引用无法解决的(完美转发除外,我已经知道了)?
是的。为了使swap
技巧发挥作用(或至少以最佳方式工作),类必须设计为在构造时处于空状态。想象一个vector
实现总是保留一些元素,而不是一开始就完全是空的。从这种默认构造的vector
与已经存在的vector
交换将意味着进行额外的分配(在此vector
实现的默认构造函数中)。
通过实际移动,默认构造的对象可以分配数据,但移动的对象可以保持未分配状态。
另外,请考虑以下代码:
std::unique_ptr<T> make_unique(...) return std::unique_ptr<T>(new T(...));
std::unique_ptr<T> unique = make_unique();
如果没有语言级别的移动语义,这是不可能的。为什么?因为第二个说法。按照标准,这是复制初始化。顾名思义,这要求类型是可复制的。 unique_ptr
的全部意义在于它是独一无二的。 IE:不可复制。
如果移动语义不存在,则不能返回它。哦,是的,您可以谈论复制省略等,但请记住:省略是一种优化。符合标准的编译器仍然必须检测到复制构造函数存在并且是可调用的;编译器可以选择不调用它。
所以不,你不能只用swap
模拟移动语义。坦率地说,即使你可以,你为什么会想要?
您必须自己编写swap
实现。所以如果你有一个有 6 个成员的类,你的 swap
函数必须依次交换每个成员。递归遍历所有基类等。
C++11(虽然不是 VC10 或 VC2012)允许编译器为您编写移动构造函数。
是的,在某些情况下,您可以在没有它的情况下逃脱。但它们看起来非常老套,很难理解你为什么要那样做,最重要的是,它们会让读者和作者都感到困难。
当人们为了性能而想做很多事情时,这会使他们的代码读起来乏味、难以编写并且容易出错(在类中添加另一个成员而不将其添加到 swap
函数中,因为例如),那么您正在寻找可能应该成为语言一部分的东西。尤其是当它是编译器可以很容易处理的东西时。
【讨论】:
很棒的答案!不过只是一个后续行动:知道这是否全面吗? (即,您知道但没有提及的任何其他原因?) +1,注意 Bjarne Stroustrup 在没有 rvalue-ref 的情况下实现了 move semantics 并且我出于同样的目的使用了swap
技巧,而完美转发理论上可以通过很多痛苦来实现(参数数量呈指数爆炸式增长)。但是 rvalue-semantics 是一个启用功能,因为手动实现它的成本(和/或危险)使得它在实践中不可能。我认为unique_ptr
根本无法在 C++03 中实现。但是可以把它想象成 lambdas:它们不会让你做任何以前不能做的事情,它只会提高可用性。
+1,更多信息:考虑实现通用 vector<T>::erase(iterator)
成员:它必须将数组中的元素向后“移动”1。如果您使用“交换”实现“移动”,那么您已经大大惩罚了vector<int>::erase(iterator)
。如果你用复制赋值来实现“移动”,你会大大惩罚vector<vector<int>>::erase(iterator)
。要优化这两种情况,您需要“移动”在一种情况下表示“类似交换”,在另一种情况下表示“类似复制”。继续...
... 沿着这条路继续走几年(同时还要尝试解决 auto_ptr 问题和完美转发),您最终会重新发明与右值引用非常相似的东西。 :-)以上是关于什么时候需要参考 R 值?的主要内容,如果未能解决你的问题,请参考以下文章