厚厚君

导航

右值引用和强制移动语义

假设X是一个类型,那么X&&称作X的右值引用类型,于是为了区分,我们称X&为X的左值引用类型。

右值引用和左值引用很相似,当然也有一些细微的不同点:当我们重载函数解析的时候,参考下面的例子:

void foo(X& x); 
void foo(X&& x); 

X x;
X foobar();

foo(x); // 参数是左值,所以调用foo(X&)
foo(foobar()); // 参数是右值,所以调用foo(X&&)

//这些都是在编译时刻确定的。

在任何时候,你都可以去实现这两种引用类型的重载,但是在大多数情况下,我会建议你在拷贝和赋值构造的时候使用这种重载比较好。

X& X::operator=(X const & rhs); 
X& X::operator=(X&& rhs)
{
  // 移动语义:交换this和rhs
return *this; }

拷贝构造同理。

警告:在上面的实现中,交换this和rhs是不够好的,我们会在之后讨论这点。

注意:

如果你实现了

void foo(X&);

但是没实现

void foo(X&&);

那么很明显foo将会调用参数为左值的表达式,参数为右值的情况将不会通过编译。

如果你实现了

void foo(X const &);

但是没有实现

void foo(X&&);

那么无论作用在左值或者右值上,foo都会成功。但是他不会区分左值和右值。

但是如果你实现了

void foo(X&&);

但是没有实现其他两个重载,对于右值来说当然是ok的,但是对左值是行不通的。

之前我们说到的move语义,是对右值进行操作的,但是很可怕的是,你同样可以对左值进行move操作。

最明显的例子就是std中的swap操作。

template<class T>
void swap(T& a, T& b) 
{ 
  T tmp(a);
  a = b; 
  b = tmp; 
} 

X a, b;
swap(a, b);

上面操作的表达式均不是右值表达式,而且也没有利用move constructor,但是我们很明确的知道使用移动语义的好处,所以我们当然希望在这里也使用到移动语义。

于是有人来拯救我们了,c++11中有一个函数叫std::move,他的作用就是把他的参数转成右值然后返回。

所以在c++11中的swap是这样子的:

template<class T> 
void swap(T& a, T& b) 
{ 
  T tmp(std::move(a));
  a = std::move(b); 
  b = std::move(tmp);
} 

如果T没有实现相应的构造函数(右值引用),那么swap操作将会像之前的行为一样(调用拷贝构造)。

使用move有很多好处:

1.对于那些实现了move语义的操作,你会得到一些性能上的提升。

2.STL容器通常要求元素支持拷贝操作,当然如果我们支持move操作,这也会满足这些容器的要求。

 但是:

我们看下面这个操作

a=std::move(b);

如果move的内部实现是一个swap操作,那么a和b的值最终会交换。

所 以这个操作完成后,a所携带的资源并不会被释放,其实a的资源会不会被释放的关键取决于b的生存周期,或者说b所占的资源此时代表了当初那个应该被释放的 资源,然而这个资源什么时候最终会被释放呢,我们无法确定。当然如果说这个释放的时间是没有副作用的,那就皆大欢喜了,但是如果,万一这个资源必须在拷贝 的时候被释放掉,那我们非做不可,而不是做一下简单的swap。

X& X::operator=(X&& rhs)
{

  // 提前释放那些有可能产生副作用的资源
  // 确保对象目前的状态是可析构的和可拷贝的 
// swap操作
  
  return *this;
}

 

posted on 2015-04-15 13:02  厚厚君  阅读(884)  评论(0编辑  收藏  举报