C++11右值引用和转发型引用教程详解

2020-01-06 18:30:57丽君

与移动语义相对,传统的拷贝语义(copy semantics)是指某个对象拷贝(复制)另一个对象所拥有的外部资源并获得新生资源的所有权。拷贝语义的具体实现(即一次that对象到this对象的拷贝(copy))通常包含以下若干步骤:

如果this对象自身也拥有资源,释放该资源 拷贝(复制)that对象所拥有的资源 将this对象的指针或句柄指向新生的资源 如果that对象为临时对象(右值),那么拷贝完成之后that对象所拥有的资源将会因that对象被销毁而即刻得以释放

上述步骤可简单概括为①释放this(this非空时)②拷贝that③释放that(that为右值时)
拷贝语义通常在拷贝构造器和拷贝赋值运算符中得以具体实现。两者的区别在于拷贝构造对象时this对象为空因而①释放this无须进行。

比较移动语义与拷贝语义的具体步骤可知,在赋值或构造对象时,

如果源对象that为左值,由于两者效果不同(移动that ≠ 拷贝that),此时移动语义不能用来替换拷贝语义。

如果源对象that为右值,由于两者效果相同(移动that = 拷贝that + 释放that),此时廉价的移动语义(通过指针操作来移动资源)便可以用来替换昂贵的拷贝语义(生成,拷贝然后释放资源)。

由此可知,只要在进行相关操作(比如赋值或构造)时,采取适当的左右值重载策略区分源对象的左右值属性,根据其左右值属性分别采用拷贝语义和移动语义,移动语义问题便可以得到解决。

下面用MemoryBlock这个自我管理内存块的类来具体说明移动语义问题。


#include <iostream> 
class MemoryBlock 
{ 
public: 
  // 构造器(初始化资源) 
  explicit MemoryBlock(size_t length) 
    : _length(length) 
    , _data(new int[length]) 
  { 
  } 
  // 析构器(释放资源) 
  ~MemoryBlock() 
  { 
    if (_data != nullptr) 
    { 
      delete[] _data; 
    } 
  } 
  // 拷贝构造器(实现拷贝语义:拷贝that) 
  MemoryBlock(const MemoryBlock& that) 
    // 拷贝that对象所拥有的资源 
    : _length(that._length) 
    , _data(new int[that._length]) 
  { 
    std::copy(that._data, that._data + _length, _data); 
  } 
  // 拷贝赋值运算符(实现拷贝语义:释放this + 拷贝that) 
  MemoryBlock& operator=(const MemoryBlock& that) 
  { 
    if (this != &that) 
    { 
      // 释放自身的资源 
      delete[] _data; 
      // 拷贝that对象所拥有的资源 
      _length = that._length; 
      _data = new int[_length]; 
      std::copy(that._data, that._data + _length, _data); 
    } 
    return *this; 
  } 
  // 移动构造器(实现移动语义:移动that) 
  MemoryBlock(MemoryBlock&& that) 
    // 将自身的资源指针指向that对象所拥有的资源 
    : _length(that._length) 
    , _data(that._data) 
  { 
    // 将that对象原本指向该资源的指针设为空值 
    that._data = nullptr; 
    that._length = 0; 
  } 
  // 移动赋值运算符(实现移动语义:释放this + 移动that) 
  MemoryBlock& operator=(MemoryBlock&& that) 
  { 
    if (this != &that) 
    { 
      // 释放自身的资源 
      delete[] _data; 
      // 将自身的资源指针指向that对象所拥有的资源 
      _data = that._data; 
      _length = that._length; 
      // 将that对象原本指向该资源的指针设为空值 
      that._data = nullptr; 
      that._length = 0; 
    } 
    return *this; 
  } 
private: 
  size_t _length; // 资源的长度 
  int* _data; // 指向资源的指针,代表资源本身 
}; 
MemoryBlock f() { return MemoryBlock(50); } 
int main() 
{ 
  MemoryBlock a = f();      // 调用移动构造器,移动语义 
  MemoryBlock b = a;       // 调用拷贝构造器,拷贝语义 
  MemoryBlock c = std::move(a);  // 调用移动构造器,移动语义 
  a = f();            // 调用移动赋值运算符,移动语义 
  b = a;             // 调用拷贝赋值运算符,拷贝语义 
  c = std::move(a);        // 调用移动赋值运算符,移动语义 
}