我看到很多人在理解右值引用时遇到问题。一旦新标准出台, “普通”C++ 代码(例如,使用标准库但未实现它)是否需要了解右值引用,或者人员/代码是否可以假装右值引用不存在?右值引用主要关注库实现者还是所有程序员?
8 回答
在 GMan(又名 gmannickg)关于该主题的博客文章中,我没有看到任何不同意的地方:
http://blackninjagames.com/?p=95
总结:任何了解复制和交换(以及通常的三规则)的人都应该了解移动语义(以及因此的右值引用),因为最佳实践习语会因此而改变。因此,我认为任何人都关心 C++。
我认为您不必知道,因为当然复制和交换仍然有效,它仍然是异常安全的,等等。而且我想有人可能会争论“普通”C++代码和程序员是否甚至需要知道复制和交换。我希望答案是“嗯,嗯,很明显”,但你永远不知道。
当然,可以开始使用 C++0x 的时间点因地而异。很快忘记 C++03 可能不是一个好主意。
例如,如果您可以依赖移动分配,则在对复制省略无效的上下文中按值返回大量集合成为更好的选择。这不是在 C++03 中无效的东西在 C++0x 中有效的情况,而是在 C++03 中非常慢的代码,你可以解决(即使只有一个很好的位置swap
) ,在 C++0x 中变得很好。因此,如果您假装右值引用不存在,您将继续解决您不需要的事情。如果您进行跳跃,那么它可能会干净地向后移植到 C++03,但运行速度很慢,这很痛苦。
所以聪明的事情可能是了解它,并且在许多情况下假装它不存在。
至少在大多数情况下,只要您愿意,您就可以忽略它们。(重新)编写标准库以使用它们可以进行相当大的优化和一些新功能,但主要目标之一是现有代码应该继续正常工作(尽管在某些情况下更快)。
支持现有代码意味着任何觉得它的人都可以继续以与以前相同的方式编写代码,并且也不会受到影响(同样,除了他们使用的某些库“东西”可能更快)。
同时,有些人可能想要学习至少一些经验法则,让他们对右值引用进行一些简单的使用,这有一些很好的理由。一个明显的例子是人们现在经常遇到的一个问题:他们有一个类,复制对象实际上没有意义,但他们仍然希望能够将这些对象放在一个集合中(例如,向量)。
他们可以通过使该类的对象可移动但不可复制来做到这一点。一个典型的例子就是流——现在,你不能将任何类型的 iostream 对象放入一个集合中,但是在 C++0x 中你可以。
另一种可能性是使用“完美转发”。这可以使将以前的许多单独的功能合并为一个具有非常微不足道的前端的功能变得更加容易,这些前端都转发给真正的工作。你总是可以不这样做,但在某些情况下,它可以使代码的重复性大大降低。
大多数人可以假装他们不存在,他们只会看到好处(在某些情况下更快的代码)。
请记住,C++0x 将 99.9% 向后兼容,因此所有现有代码仍然可以工作,这意味着您可以继续编写 C++0x 代码,就像它是 C++03 一样。
如果您想获得右值引用的好处,那么您当然需要了解它们,但大多数时候这只是库编写者关心的问题。
你可能不需要了解右值引用,因为你需要编写一些函数,但你可能会从理解移动语义、了解什么是移动构造函数等中受益。如果您想使用 unique_ptr (并获得性能优势,因为它没有引用计数),那么您几乎肯定需要使用 std::move (例如,进出集合),我无法想象在“如果我在这里放一个 & 或 * 怎么办?” 一个典型的二年级学生处理指针的方式。
我刚刚在柏林的 Tech Ed Europe 上做了一个“以 C++0x 为特色的 VC++ 中的新功能”演讲,其中有一张关于右值引用的幻灯片,我基本上说“你需要知道这些在这里,因为它们使 STL 更快”但我没有进一步钻探。这似乎比我在其他会谈中的(更长的)变体更能被与会者接受。
我同意,RValue 引用并不是每个人的一杯茶——杯子很大。
因此,我告诉 C++0x 的学习者,开始并利用RValue 引用的最佳方法是使用 STL 并随着时间的推移接受它的设计模式。很可能通过这种方式,您可以从RValue 引用中受益,而无需事先完全理解它们。
Scott Meyers 将 C++0x 特性分为:
- 适合所有人的功能。
- 类作者的功能。
- 图书馆作者的功能。
RValues 绝对是图书馆的作者。
在 c++11 中,我会通过 && 传递我计划“存储”或“消费”的任何内容。这会产生最有效的代码,但会“破坏”左值兼容性并迫使我考虑每种方法都有两个(或更多)版本。不幸的是,随着参数数量的增加,你会得到 rvalue 和 const& 排列的爆炸式增长,这使得函数指针变得更加“丑陋”。
我没有提供两个(或更多)重载,一个用于左值,一个用于右值,我决定只提供右值重载,如果用户想要传递一个左值,他们用 std::ref()、std::cref 包装()、std::move() 或我自己的助手 copy() 使副本显式并将左值转换为右值。(std::ref() 和 std::cref() 只能在参数是模板参数时工作......在这种情况下,该方法可能会使用 std::forward<>() 来处理移动的自动检测vs 复制语义。
template<typename T>
T copy( const T& v ) { return v; }
如果我正在编写一个不“存储”或“消耗”输入的方法,那么我使用 const&
我可能应该澄清一下,我将 const& 用于任何不受益于移动语义(无深拷贝)的类型。但是当您编写模板代码时,您不想对类型做出这种假设。
我正在考虑的另一种做法是使某些类“仅显式复制”。
class test {
public:
test(){...};
test( test&& t ){...}
private:
template<typename T>
friend T copy(const T&t);
test& operator=(const test&); // not implemented
test( const test& t ){...}
std::vector<char> large_data;
};
int main( int argc, char** argv ) {
test x;
test y = copy(x);
test z = x; // error, test(const test&) is private...
return 0;
}
我会将它用于包含任何“可移动成员”的任何类,例如字符串、向量、地图等。从技术上讲,我没有理由不能复制它们,但为什么我不应该尝试在任何地方移动语义,除非我真的要复制吗?使用隐式副本,编译器无法警告我我正在做一些可能很昂贵的事情。
如果你没有实现一个值语义类,你真的不需要知道。
编辑:或完美转发。但这同样主要是图书馆的使用。