场景:
- C++ 标准库使用比如vector::push_back 等这类函数时,会对参数的对象进行复制,连数据也会复制.这就会造成对象内存的额外创建, 本来原意是想把参数push_back进去就行了.
- C++11 提供了std::move 函数来把左值转换为右值, 而且新版的push_back也支持&&参数的重载版本,这时候就可以高效率的使用内存了.
- 对指针类型的标准库对象并不需要这么做.
参考:
说明:
1、move用来表示一个对象t可以被“移动”,即允许资源从t有效地转移到另一个对象,即资源所有权转移。
// Simple move constructor
A(A&& arg) : member(std::move(arg.member)) // the expression "arg.member" is lvalue
{}
// Simple move assignment operator
A& operator=(A&& other) {
member = std::move(other.member);
return *this;
}
特别地,std::move产生一个将亡值的表达式来标识它的参数t。它完全等价于一个static_cast到一个右值引用类型。每个C ++ 表达式(带有其操作数的运算符,文字,变量名等)都具有两个独立的属性:type和value category。每个表达式都有某种非引用类型,每个表达式恰好属于三个主要值类别之一:左值、将亡值、右值。区分表达式的左右值属性有一个简便方法:若可对表达式用&符取址,则为左值,否则为右值。更多解释请参阅 Value categories
2、注意,标准库对象支持moved from的左值在moved 之后它的对象原值是有效的(可以正常析构),但是是unspecified的,可以理解为空数据,但是这个对象的其他方法返回值不一定是0,比如size().所以,moved from 之后的对象最好还是不要使用吧?(如有不正确理解,请告知)
3、对本身进行move,并赋值给本身是undefined的行为.
std::vector<int> v = {2, 3, 3};
v = std::move(v); // undefined behavior
std::move 的函数原型.
/**
* @brief Convert a value to an rvalue.
* @param __t A thing of arbitrary type.
* @return The parameter cast to an rvalue-reference to allow moving it.
*/
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
结构体 remove_reference 的原型,就是重载了多个结构体模板来获取原类型 type.
/// remove_reference
template<typename _Tp>
struct remove_reference
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp type; };
本主题描述如何为C ++类编写move构造函数和move赋值运算符。移动构造函数使右值对象拥有的资源可以移动到左值而不进行复制。有关移动语义的更多信息,请参见Rvalue参考声明符:&&。
深拷贝和move的区别
深拷贝
本主题以以下C ++类为基础,该类MemoryBlock管理内存缓冲区。
//MemoryBlock.cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
class MemoryBlock
{
public:
explicit MemoryBlock(size_t length):_length(length),_data(new int[length])
{
cout << "In MemoryBlock(size_t). length = "
<< _length << "." << endl;
}
~MemoryBlock()
{
cout << "In ~MemoryBlock(). length = "
<< _length << ".";
if(_data!=nullptr)
{
cout << " Deleting resource.";
delete[] _data;
}
cout<<endl;
}
MemoryBlock(const MemoryBlock& other):_length(other._length),
_data(new int[other._length])
{
cout << "In MemoryBlock(const MemoryBlock&). length = "
<< other._length << ". Copying resource." << endl;
copy(other._data,other._data+other._length,this->_data);
}
MemoryBlock& operator = (const MemoryBlock& other)
{
cout << "In operator=(const MemoryBlock&). length = "
<< other._length << ". Copying resource." << std::endl;
if(this!=&other)
{
delete[] this->_data;
this->_length = other._length;
this->_data = new int[this->_length];
copy(other._data,other._data + this->_length,this->_data);
}
return *this;
}
size_t Length() const
{
return _length;
}
private:
size_t _length;
int* _data;
};
int main(int argv,char* argc[])
{
// Create a vector object and add a few elements to it.
vector<MemoryBlock> v;
v.push_back(MemoryBlock(25));
v.push_back(MemoryBlock(75));
// Insert a new element into the second position of the vector.
v.insert(v.begin() + 1, MemoryBlock(50));
return 0;
}
# g++ MoveMemoryBlock.cpp -o main -std=c++11 && ./main
In MemoryBlock(size_t). length = 25.
In MemoryBlock(const MemoryBlock&). length = 25. Copying resource.
In ~MemoryBlock(). length = 25. Deleting resource.
In MemoryBlock(size_t). length = 75.
In MemoryBlock(const MemoryBlock&). length = 75. Copying resource.
In MemoryBlock(const MemoryBlock&). length = 25. Copying resource.
In ~MemoryBlock(). length = 25. Deleting resource.
In ~MemoryBlock(). length = 75. Deleting resource.
In MemoryBlock(size_t). length = 50.
In MemoryBlock(const MemoryBlock&). length = 50. Copying resource.
In MemoryBlock(const MemoryBlock&). length = 25. Copying resource.
In MemoryBlock(const MemoryBlock&). length = 75. Copying resource.
In ~MemoryBlock(). length = 75. Deleting resource.
In ~MemoryBlock(). length = 25. Deleting resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 75. Deleting resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 25. Deleting resource.
以下过程描述了如何为示例C ++类编写move构造函数和move赋值运算符。
移动构造函数和移动赋值运算符的创建
为C ++类创建move构造函数
1、定义一个空的构造函数方法,该方法将对类类型的右值引用作为其参数,如以下示例所示:
MemoryBlock(MemoryBlock&& other)
: _data(nullptr)
, _length(0)
{
}
2、在移动构造函数中,将类数据成员从源对象分配给正在构造的对象:
_data = other._data;
_length = other._length;
3、将源对象的数据成员分配给默认值。这样可以防止析构函数多次释放资源(例如内存):
other._data = nullptr;
other._length = 0;
为C ++类创建移动分配运算符
1、定义一个空的赋值运算符,该运算符将对类类型的右值引用作为其参数,并返回对类类型的引用,如以下示例所示:
MemoryBlock& operator=(MemoryBlock&& other)
{
}
2、在移动分配运算符中,添加一个条件语句,如果您尝试将对象分配给自身,则该条件语句不执行任何操作。
if (this != &other)
{
}
3、在条件语句中,从分配给对象的对象中释放所有资源(例如内存)。
下面的示例将_data成员从分配给该对象的对象中释放出来:
// Free the existing resource.
delete[] _data;
请按照第一个过程中的步骤2和3将数据成员从源对象传输到正在构造的对象:
// Copy the data pointer and its length from the
// source object.
_data = other._data;
_length = other._length;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other._data = nullptr;
other._length = 0;
4、返回对当前对象的引用,如以下示例所示:
return *this;
以下示例显示了MemoryBlock该类的完整的move构造函数和move赋值运算符:
// Move constructor.
MemoryBlock(MemoryBlock&& other) noexcept
: _data(nullptr)
, _length(0)
{
std::cout << "In MemoryBlock(MemoryBlock&&). length = "
<< other._length << ". Moving resource." << std::endl;
// Copy the data pointer and its length from the
// source object.
_data = other._data;
_length = other._length;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other._data = nullptr;
other._length = 0;
}
// Move assignment operator.
MemoryBlock& operator=(MemoryBlock&& other) noexcept
{
std::cout << "In operator=(MemoryBlock&&). length = "
<< other._length << "." << std::endl;
if (this != &other)
{
// Free the existing resource.
delete[] _data;
// Copy the data pointer and its length from the
// source object.
_data = other._data;
_length = other._length;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other._data = nullptr;
other._length = 0;
}
return *this;
}
以下示例显示了移动语义如何提高应用程序的性能。该示例将两个元素添加到矢量对象,然后在两个现有元素之间插入一个新元素。所述vector类用途移动语义通过移动向量的元素,而不是复制它们有效地进行插入操作。
// rvalue-references-move-semantics.cpp
// compile with: /EHsc
#include "MemoryBlock.h"
#include <vector>
using namespace std;
int main()
{
// Create a vector object and add a few elements to it.
vector<MemoryBlock> v;
v.push_back(MemoryBlock(25));
v.push_back(MemoryBlock(75));
// Insert a new element into the second position of the vector.
v.insert(v.begin() + 1, MemoryBlock(50));
}
In MemoryBlock(size_t). length = 25.
In MemoryBlock(MemoryBlock&&). length = 25. Moving resource.
In ~MemoryBlock(). length = 0.
In MemoryBlock(size_t). length = 75.
In MemoryBlock(MemoryBlock&&). length = 75. Moving resource.
In MemoryBlock(MemoryBlock&&). length = 25. Moving resource.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 0.
In MemoryBlock(size_t). length = 50.
In MemoryBlock(MemoryBlock&&). length = 50. Moving resource.
In MemoryBlock(MemoryBlock&&). length = 25. Moving resource.
In MemoryBlock(MemoryBlock&&). length = 75. Moving resource.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 25. Deleting resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 75. Deleting resource.
此示例使用移动语义的版本比不使用移动语义的版本效率更高,因为它执行较少的复制,内存分配和内存释放操作。
安全的编程
为防止资源泄漏,请始终在移动分配运算符中释放资源(例如内存,文件句柄和套接字)。
为防止无法恢复的资源破坏,请在移动分配运算符中正确处理自我分配。
如果为您的类同时提供了移动构造函数和移动分配运算符,则可以通过编写移动构造函数以调用移动分配运算符来消除冗余代码。以下示例显示了移动构造函数的修订版,该构造函数调用了移动分配运算符:
// Move constructor.
MemoryBlock(MemoryBlock&& other) noexcept
: _data(nullptr)
, _length(0)
{
*this = std::move(other);
}
该 std ::move 将左值other转换成右值。
也可以看看
右值引用声明符:&&
std :: move