右值引用深入探讨

假若X这个类型重载了拷贝构造以及移动构造。

那么思考一下:

void foo(X&& x)
{
  X anotherX = x;
  // ...
}

哪个构造函数将会被调用?在这里x是右值引用类型,那么他是右值还是左值呢?之前对左值和右值的定义中已经表明了,如果我们可以对其进行取址操作,那么他就是左值,

所以你可以对右值引用进行取址操作吗?

于是,右值引用的设计者这样说:

“右值引用变量既可以是右值也可以是左值,这取决于:如果他有名字,那么他是左值,否则,为右值”。

所以在上面的例子中,x是有名字的,所以他是左值!所以调用的是拷贝构造函数。

接着看下面这个例子:

X&& goo();
X x = goo(); // 调用 X(X&& rhs) 因为goo()的返回值没有名字。

其实这样也很容易理解,当右值引用有名字的时候,这代表这个变量有可能在其他地方被引用到,那么一旦被看作是右值,那么就危险了,因为右值的意思是,这个值是无所谓的,或者说这个值里面的内容我们不关心,所以我们才会对他进行swap操作,或者其他对其有副作用的操作,因为反正右值里面的东西是“垃圾堆”,我们忘里面扔就好了,但是如果他会被其他人引用到,那么这个垃圾堆就会产生危害。而当右值引用没名字的时候,我们也无法对他进行引用,所以我们可以大胆放心的把他当作右值进行使用。

看下面一个例子:

Base(Base const & rhs); // non-move semantics
Base(Base&& rhs); // move semantics

那么他的子类Derived应该如何实现呢?

Derived(Derived const & rhs) 
  : Base(rhs)
{
  // Derived-specific stuff
}

这样写很正常,没什么问题,注意:会有一个类型转换在里面。

那move constructor怎么写?

Derived(Derived&& rhs) 
  : Base(rhs) // wrong: rhs is an lvalue
{
  // Derived-specific stuff
}

当然,我们想调用的是Base的move构造,但是这里参数rhs是有名字的,所以我们调用错了,这里就可以使用std::move来进行转换一下。

Derived(Derived&& rhs) 
  : Base(std::move(rhs)) // good, calls Base(Base&& rhs)
{
  // Derived-specific stuff
}

 接下来,看下面的代码:

X foo()
{
  X x;
  // perhaps do something to x
  return x;
}

同样,X重载了拷贝构造以及移动构造。

这时候你的小聪明作祟,心想,这样不是拷贝了好几次嘛,于是你把代码改成这样:

X foo()
{
  X x;
  // perhaps do something to x
  return std::move(x); // making it worse!
}

不幸的是,这样反倒使得效率更低。我们都知道现代的编译器会进行一些优化,在不影响代码逻辑的前提下。

那么我相信大家一定听说过返回值优化这个东西,所以如果你使用第一种返回方式,那么X的构造可能只有一次,而第二种小聪明的返回方式会调用默认构造+移动构造,反而效率更低。所以为了更好的使用移动语义,你需要对编译器优化进行一定的了解,否则有可能画蛇添足。

原文地址:https://www.cnblogs.com/houhoujun/p/4416128.html