std::move
std::move 是 C++11 引入的一个非常有用的函数模板。std::move 的主要作用是将其参数转换为右值引用(rvalue reference),从而允许我们利用移动语义(move semantics)来优化资源的管理,特别是在涉及大量数据移动或对象所有权转移的场景中。
简单来说,std::move 并不移动任何东西;它仅仅是对对象进行了“类型转换”,将其视为一个右值引用。这使得对象可以被移动(如果它的类型支持移动语义)而不是被复制。移动通常比复制更高效,因为它允许资源的直接转移而不是复制整个资源。
触发移动构造函数或移动赋值运算符:当一个对象被 std::move 标记后,如果目标对象的类型实现了移动构造函数(Move Constructor)或移动赋值运算符(Move Assignment Operator),这些特殊成员函数将被调用,从而实现资源的转移。
前提知识
c++中的左值和右值,即lvalue,rvalue,它们是表达式的两种分类:
- lvalue
左值是指那些可以出现在赋值运算符左侧的表达式。通常表示一个对象或变量的标识符,它们具有持久的内存地址。可以通过取地址运算符&获取其地址。
特点:具有明确的内存地址。可以出现在赋值语句的左边。常见的左值包括变量、对象成员、数组元素等。 - rvalue
右值是一个临时的、不具有标识符的表达式,通常表示一个值而不是一个具体的对象。右值不能位于赋值操作符的左边,因为它们不具备可修改性。右值通常没有明确的内存位置,因此不能通过取地址运算符获取其地址。
特点:通常是临时的、匿名的。不能出现在赋值语句的左边。常见的右值包括常量、字面值、临时对象、函数返回的临时结果等。
总结:左值可以理解为“地址值”,因为它们通常代表了一个具体的内存位置;而右值可以理解为“数据值”,因为它们通常代表了一个具体的数值或计算结果,但没有与之关联的内存位置。
使用示例
- 右值引用&&
&&右值引用支持移动语义的实现,这可以显著提高你的应用程序的性能。移动语义允许你编写代码,将资源(如动态分配的内存)从一个对象转移到另一个对象。移动语义之所以有效,是因为它允许资源从临时对象(在程序中其他地方无法引用的对象)中转移。
为了实现移动语义,你通常需要在你的类中提供一个移动构造函数(可选地,还可以提供一个移动赋值运算符(operator=))。当源是右值时,复制和赋值操作将自动利用移动语义。
与默认的复制构造函数不同,编译器不提供默认的移动构造函数。有关如何编写移动构造函数以及如何在你的应用程序中使用它的更多信息,请参阅“移动构造函数和移动赋值运算符(C++)”。
当编译器无法使用返回值优化(RVO)或命名返回值优化(NRVO)时,移动语义也非常有用。在这些情况下,如果类型定义了移动构造函数,编译器将调用它。
为了更好地理解移动语义,请考虑将元素插入vector对象的示例。如果vector对象的容量被超出,vector对象必须为其元素重新分配内存,并将每个元素复制到另一个内存位置以为插入的元素腾出空间。在插入操作复制元素时,它会创建一个新元素,调用复制构造函数将前一个元素的数据复制到新元素,然后销毁前一个元素。移动语义允许你直接移动对象,而无需执行昂贵的内存分配和复制操作。
为了在vector示例中利用移动语义,你可以编写一个移动构造函数来将一个对象的数据移动到另一个对象。
std::vector<int> vec1 = {
1, 2, 3, 4, 5};
std::vector<int> vec2;
// 使用 std::move 来指示 vec1 的内容将被移动到 vec2
vec2 = std::move(vec1);
// 此时,vec1 的状态是未定义的,因为它的内容已经被“移动”走了
// vec2 则包含了原 vec1 的内容
#include <iostream>
using namespace std;
class A {
public:
int x;
A(int x) : x(x) {
std::cerr << "Constructor" << std::endl ;}
A(A& a) : x(a.x) {
cout << "Copy Constructor" << endl ;}
A& operator=(A& a){
x = a.x; cout << "Copy Assignment operator" << endl; return *this;}
A(A&& a) : x(a.x) {
cout << "Move Constructor" << endl; }
A& operator=(A&& a) {
x = a.x; cout << "Move Assignment operator" << endl; return *this; }
};
A GetA() {
return A(1); }
A&& MoveA(){
return A(1); }
int main()
{
A a(1); // 调用构造函数 [output] Constructor
A b = a; // 创建新对象b,使用a初始化b,因此调用拷贝构造函数。[output] Copy Constructor
A c(a); // 创建新对象c,使用a初始化c,因此调用拷贝构造函数。[output] Copy Constructor
b = a; // 使用a的值更新对象b,没有创建新对象,所以调用拷贝赋值运算符。[output] Copy Assignment operator
// 使用临时对象A(1)初始化d,由于临时对象是一个右值,所以理论上是调用构造函数和移动构造函数,
// 但是实际编译器会进行返回值优化,不会调用拷贝和移动拷贝,因此输出只是一个Constructor
// [output] Constructor
A d = A(1);
// 创建新对象e,使用a的值初始化e,但调用std::move(a)将左值a转化为右值,所以调用移动构造函数。
// [output] Move Constructor
A e = std::move(a);
// 创建新对象f,使用GetA()函数返回的临时对象初始化f,由于临时对象是右值,同5 。
// [output] Constructor
A f = GetA();
// 没有创建新对象,也不更新任何对象,只是将MoveA()的返回值绑定到右值引用g。因此不调用构造函数,也不调用赋值运算符。
// [output] Constructor
A&& g = MoveA();
// 使用临时对象A(1)更新d,因为不需要创建新对象,所以调用移动赋值运算符。
// [output]Constructor, Move Assignment operator
d = A(1);
}
经典使用示例:
#include <iostream>
#include <string>
#include <vector>
typedef unsigned char uchar;
class Buffer
{
private:
int capacity;
int length;
unsigned char* buf;
public:
explicit Buffer(int capacity) :capacity(capacity), length(0){
buf = capacity == 0 ? nullptr : new unsigned char[capacity];
std::cout << "Buffer Constructor. c: " << capacity << std::endl;
}
// 为了简化代码,在拷贝构造中,可以调用复制操作,但是需要注意的是:
// operator=中会将this 的buf 进行删除,由于构造之前,对象还没有,那么就需要
// buf 设置成null
Buffer(const Buffer& buffer):Buffer(0)
{
std::cout << "Buffer Copy Constructor." << std::endl;
*this = buffer;
}
Buffer& operator=(const Buffer& buffer)
{
std::cout << "Buffer Copy." << std::endl;
if (this != &buffer)
{
this->capacity = buffer.capacity;
this->length = buffer.length;
delete[] this->buf;
this->buf = new uchar[this->capacity];
std::copy(buffer.buf, buffer.buf + buffer.capacity, this->buf);
}
return *this;
}
// 移动构造
Buffer(Buffer&& buffer) noexcept :Buffer(0) {
std::cout << "Move Constructor. c:" << buffer.capacity << std::endl;
// buffer在这个函数里面并不能看作右值,因为在这个函数
// 内部它并不是将亡值,整个函数都有效的,所以应该看作
// 左值,那么 *this = buffer ,自然调用对是上面的copy 赋值
*this = std::move(buffer);
// std::move 主要做的是类型强转
// 使用这个也是没问题的 *this = static_cast<Buffer&&>(buffer);
}
Buffer& operator=(Buffer&& buffer) noexcept
{
std::cout << "Buffer Move Copy.c:" << buffer.capacity << std::endl;
if (this == &buffer) return *this;
this->capacity = buffer.capacity;
this->length = buffer.length;
// 左边对象 原来的buf需要删除, 注意,在上面的移动构造中
// 一定要将buf初始化为nullptr 不然,此处删除会报错
delete[] this->buf;
this->buf = buffer.buf;
buffer.capacity = 0;
buffer.length = 0;
buffer.buf = nullptr;
return *this;
}
~