与移动语义相对,传统的拷贝语义(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); // 调用移动赋值运算符,移动语义
}










