获取const
左值引用和获取右值引用是两件不同的事情。
相似之处:
- 两者都不会导致复制或移动,因为它们都是引用。引用只是引用一个对象,它不会以任何方式复制/移动它。
差异:
让我们看一些例子:
取const
左值参考:void f(const T& t);
传递左值:
T t; f(t);
这里,t
是一个左值表达式,因为它是对象的名称。const
左值引用可以绑定到任何东西,所以很乐意t
通过引用传递。没有任何东西被复制,没有任何东西被移动。
传递一个右值:
f(T());
这里,T()
是一个右值表达式,因为它创建了一个临时对象。同样,const
左值引用可以绑定到任何东西,所以这没关系。没有任何东西被复制,没有任何东西被移动。
在这两种情况下,t
函数内部都是对传入对象的引用。它不能被引用修改const
。
取一个右值引用:`void f(T&& t);
传递左值:
T t;
f(t);
这会给你一个编译器错误。右值引用不会绑定到左值。
传递一个右值:
f(T());
这很好,因为右值引用可以绑定到右值。函数内部的引用t
将引用由T()
.
现在让我们考虑std::move
。首先要做的事情:std::move
实际上并没有移动任何东西。这个想法是你给它一个左值,然后它把它变成一个右值。这就是它所做的一切。所以现在,如果你f
采用右值引用,你可以这样做:
T t;
f(std::move(t));
这是有效的,因为虽然t
是一个左值,但它std::move(t)
是一个右值。现在右值引用可以绑定到它。
那么你为什么要使用右值引用参数呢?事实上,您不需要经常这样做,除了定义移动构造函数和赋值运算符。每当你定义一个接受右值引用的函数时,你几乎肯定想要给一个const
左值引用重载。他们应该几乎总是成对出现:
void f(const T&);
void f(T&&);
为什么这对函数有用?好吧,只要你给它一个左值(或const
右值),就会调用第一个,而只要你给它一个可修改的右值,就会调用第二个。接收到一个右值通常意味着你得到了一个临时对象,这是个好消息,因为这意味着你可以破坏它的内部并根据你知道它不会存在太久的事实来执行优化。
因此,拥有这对函数可以让您在知道自己得到一个临时对象时进行优化。
这对函数有一个非常常见的例子:复制和移动构造函数。它们通常是这样定义的:
T::T(const T&); // Copy constructor
T::T(T&&); // Move constructor
所以移动构造函数实际上只是一个复制构造函数,在接收临时对象时进行了优化。
当然,传递的对象并不总是临时对象。如上所示,您可以使用std::move
将左值转换为右值。然后它似乎是该函数的临时对象。使用std::move
基本上是说“我允许您将此对象视为临时对象。” 它是否真的被移动是无关紧要的。
但是,除了编写复制构造函数和移动构造函数之外,您最好有充分的理由使用这对函数。如果您正在编写一个函数,该函数接受一个对象,并且无论它是否是临时对象,它的行为都与它完全相同,只需按值获取该对象!考虑:
void f(T t);
T t;
f(t);
f(T());
在第一次调用中f
,我们传递了一个左值。这将被复制到函数中。在第二次调用中f
,我们传递了一个右值。该对象将被移动到函数中。看——我们甚至不需要使用右值引用来有效地移动对象。我们只是按价值计算!为什么?因为用于进行复制/移动的构造函数是根据表达式是左值还是右值来选择的。让复制/移动构造函数完成他们的工作。
至于不同的参数类型是否会导致相同的代码——这完全是一个不同的问题。编译器在as-if 规则下运行。这仅仅意味着只要程序按照标准的要求运行,编译器就可以发出它喜欢的任何代码。因此,如果这些函数碰巧做了完全相同的事情,它们可能会发出相同的代码。或者他们可能不会。但是,如果您的函数采用 const 左值引用和右值引用正在做同样的事情,那么这是一个不好的迹象。