C++ 移动语义
移动语义
C++11中提供了一个新特性-移动语义,使用std::move
实现,用法为
std::move
MyClass A;
MyClass B = std::move(A);
移动语义的意义是当一个临时变量生命周期要结束了,但是需要使用其初始化另外一个变量,例如函数的返回值(非指针),就是一个临时变量。我们知道,使用一个类去初始化另外一个类可以使用拷贝的方式,即调用类的拷贝构造函数或赋值运算符函数,为了防止类中内容出现指针悬垂情况,类的拷贝构造和赋值运算符都会写成深拷贝的模式,即需要重新申请一块内存再将数据存放进去。而移动语义则是直接占为己有,即把数据直接转移到另一个实例化的类中,避免了拷贝带来的性能花销。std::move
函数的用处就是告诉编译器,这个对象已经不需要了,可以直接转给别的类使用。
下面展示一个例子:
#include <iostream>
#include <vector>
using namespace std;
class MyClass
{
public:
MyClass(const char* _name) {
id = 0;
name = new char[256];
memset(name, 0, 256);
strcpy_s(this->name, 256, _name);
cout << "Call Constructor, name address = " << static_cast<void*>(name) << endl;
}
MyClass(const MyClass& other) {
if (this->name)
delete this->name;
this->name = new char[256];
memset(this->name, 0, 256);
strcpy_s(this->name, 256, other.name);
this->id = other.id;
cout << "Call Copy Constructor, name address = " << static_cast<void*>(name) << endl;
}
~MyClass() {
cout << "Call Destructor, name address = " << static_cast<void*>(name) << endl;
if (name)
delete[] name;
}
private:
int id;
char* name = nullptr;
};
MyClass func()
{
return MyClass("aaa");
}
int main()
{
MyClass a = move(func());
MyClass b = func();
}
运行这段代码会发现拷贝构造函数被调用了,原因是因为类中没有定义移动构造函数,编译器只能去调用拷贝构造函数。
移动构造函数
下面就来实现移动拷贝构造函数。为了实现移动语义,C++11新增了一个新特性,叫做右值引用,使用双&定义。单一&定义的是左值引用。
int a = 0;
int& b = a; // 左值引用
int&& c; // 右值引用
让编译器将一个变量匹配成右值引用的情况有两个
- 一个在执行语句后就会被释放的临时变量
- 使用std::move标记的变量
将变量匹配成右值引用是实现移动语义的基础
实现移动构造函数的思路就是移动语义的含义,直接将另一个类的数据内存直接接管,同时还需要注意一个重点,就是把传入对象的数据清除。
移动构造函数的参数就是上述说的右值引用,在上述类中添加上移动构造函数:
// 实现深拷贝类
class MyClass
{
public:
MyClass(const char* _name) {
id = 0;
name = new char[256];
memset(name, 0, 256);
strcpy_s(this->name, 256, _name);
cout << "Call Constructor, name address = " << static_cast<void*>(name) << endl;
}
MyClass(const MyClass& other) {
if (this->name)
delete this->name;
this->name = new char[256];
memset(this->name, 0, 256);
strcpy_s(this->name, 256, other.name);
this->id = other.id;
cout << "Call Copy Constructor, name address = " << static_cast<void*>(name) << endl;
}
// 移动构造函数
MyClass(MyClass&& other) noexcept {
this->id = move(other.id);
// 直接移动
this->name = other.name;
// move对原始指针没用,直接接管即可
other.name = nullptr;
cout << "Call Move Constructor, name address = " << static_cast<void*>(name) << endl;
}
MyClass& operator=(const MyClass& other) {
if (this != &other)
{
if (this->name)
delete this ->name;
this->name = new char[256];
memset(this->name, 0, 256);
strcpy_s(this->name, 256, other.name);
this->id = other.id;
}
cout << "Call operator, name address = " << static_cast<void*>(name) << endl;
return *this;
}
~MyClass() {
cout << "Call Destructor, name address = " << static_cast<void*>(name) << endl;
if (name)
delete[] name;
}
private:
int id;
char* name = nullptr;
};
总结
当出现一些只为了提供给另外一个类实例化的变量时,例如函数返回临时变量或是将一个实例化的类push_back到vector中而无其他用处,就可以使用移动语义的方式,以减少程序运行时出现的拷贝,减少性能消耗。