移动语义
如果一个对象中有堆区资源,需要编写拷贝构造函数和赋值函数。
深拷贝把对象中的堆区资源复制了一份,如果源对象(被拷贝的资源)是临时对象,拷贝完就没什么用了,这样会造成没有意义的申请资源和释放动作,如果能够直接使用源对象拥有的资源, 可以节省申请和释放时间,c++11新增的移动语义能够做到这一点。
移动构造函数语法
类名(类名&& 源对象) {.....}
移动赋值函数语法
类名& operator=类名&& 源对象) {.....}
实现类的移动构造和移动赋值函数
#include <iostream>
#include <memory>
#include<string>
#include <cstring>
using namespace std;
class AA {
public:
int * m_a = nullptr; // 指向堆区资源的指针
AA()=default; // 启用默认构造函数
void alloc() {
m_a = new int(); // 分配内存
memset(m_a, 0, sizeof(int)); // 初始化已分配的内存
}
AA(const AA &ra) { //拷贝构造函数
cout << "调用了拷贝构造函数"<<endl;
if (m_a == nullptr) alloc();
memcpy(this->m_a, ra.m_a, sizeof(int)); //把数据从源对象拷贝过来
}
AA(AA&& ra) { //移动构造函数, 参数是右值引用
cout << "调用了移动构造函数"<<endl;
if (m_a != nullptr) delete m_a; // 如果已经分配内存,先释放掉
m_a = ra.m_a; // 把资源从源对象中转移过来
ra.m_a = nullptr; //把源对象中的指针置空
}
AA& operator=(const AA& ra) { // 赋值函数
cout << "调用了赋值函数"<<endl;
if (this == &ra) return *this;
if (m_a == nullptr) alloc();
memcpy(this->m_a, ra.m_a, sizeof(int)); //把数据从源对象拷贝过来
return *this;
}
// 移动赋值函数
AA& operator=(AA&& ra) {
cout << "调用了移动赋值函数"<<endl;
if (this == &ra) return *this;
if (m_a != nullptr) delete m_a; // 如果已经分配内存,先释放掉
m_a = ra.m_a; // 把资源从源对象中转移过来
ra.m_a = nullptr; //把源对象中的指针置空
return *this;
}
~AA() {
if (m_a != nullptr)
{
delete m_a;
m_a = nullptr;
}
}
};
int main() {
// 如果形参是左值 就调用拷贝构造函数,如果形参是右值,就调用移动构造函数
AA a1; // 创建对象a1
a1.alloc(); // 分配堆区资源
*(a1.m_a) = 10; //给堆区内存赋值
cout << "a1.m_a=" << *a1.m_a << endl;
AA a2 = a1; // 将调用拷贝构造函数
cout << "a2.m_a=" << *a2.m_a << endl;
AA a3;
a3 = a1; // 将调用赋值构造函数
cout << "a3.m_a=" << *a3.m_a << endl;
cout << "----------------" << endl;
// 返回aa类对象的lambda函数
auto f =[]() {
AA aa;
aa.alloc();
*(aa.m_a) = 55;
return aa;
};
AA a4 = f(); // lambda函数返回的临时对象是右值,将调用移动构造函数
cout << "a4.m_a=" << *a4.m_a << endl;
AA a5;
a5 = f(); // lambda函数返回的临时对象是右值,将调用移动赋值函数
cout << "a5.m_a=" << *a5.m_a << endl;
return 0;
}
-
对于一个左值,会调用拷贝构造函数,但是有些左值是局部变量,声明周期也很短,c++11提供了一个std::move()方法来将左值转义为右值,从而方便使用移动语义。
其实就是告诉编译器 -
如果没有提供移动构造/赋值函数, 只提供了拷贝构造/赋值函数,编译器找不到移动构造/赋值函数,就去寻找拷贝构造/赋值函数
-
c++11 中的所有容器,都实现了移动语义,避免对含有资源的对象,发生无谓的拷贝。
-
移动语义,对拥有资源(内存,文件句柄) 的对象有用,如果是基本类型,使用移动语义没有意义。
int main() {
// 如果形参是左值 就调用拷贝构造函数,如果形参是右值,就调用移动构造函数
AA a1; // 创建对象a1
a1.alloc(); // 分配堆区资源
*(a1.m_a) = 10; //给堆区内存赋值
cout << "a1.m_a=" << *a1.m_a << endl;
AA a2 = move(a1); // move后,a1左值被转义为右值,将调用移动构造函数
cout << "a2.m_a=" << *a2.m_a << endl;
AA a3;
a3 = move(a1); // move后,a1左值被转义为右值,,将调用移动赋值函数
cout << "a3.m_a=" << *a3.m_a << endl;
return 0;
}
image.png
网友评论