1、右值引用
1.1 背景
c++98中的引用很常见,就是给变量取个别名,具体可以参考c++积累7
在c++11中,增加了右值引用的概念,所以c++98中的引用都称为左值引用
1.2 定义
右值引用就是给右值取个名字,右值有了名字之后就成了普通变量,可以像使用左值一样使用。
语法:数据类型&& 变量名=右值
示例:
#include <iostream>
class AA {
public:
int m_a = 9;
};
AA getTemp() {
return AA();
}
int main() {
using namespace std;
int &&a = 3; // 3是右值,给它起个名字叫a
int b = 8; // b 是左值, 8是右值
int &&c = b + 5; // b+5是右值,给它取个名字叫c
AA &&aa = getTemp();// getTemp()返回值是右值(临时变量),给它起个名字叫aa
cout << "a= " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
cout << "aa.m_a= " << aa.m_a << endl;
return 0;
}
1.3 常量左值引用
常量左值引用是一个万能的引用类型,它可以绑定非常量左值、常量做值、右值,在绑定右值的时候,常量左值引用可以像右值引用一样将右值的生命期延长,缺点是只能读不能改
int a = 1;
const int &ra = a; // a是非常量左值
const int b = 2;
const int &rb = b; // b是常量左值
const int &rc = 1; // 1是右值
2、移动语义
2.1 背景
如果一个对象中有堆区资源,需要编写拷贝构造函数和赋值函数,实现深拷贝。
深拷贝把对象中堆区资源复制了一份,如果资源(被拷贝的资源)是临时对象,拷贝完就没有什么意义了,这样会造成没有意义的资源申请和释放操作。
如果能够直接使用对象拥有的资源,可以节省资源申请和释放的时间。c++11增加的移动语义就能够做到这一点。
2.2 定义
移动语义增加两个构造函数:移动构造函数 、 移动赋值函数
移动构造函数语法:
类名(类名&& 源对象){…}
移动赋值函数语法:
类名& operator=(类名&& 源对象){…}
demo:
#include <iostream>
#include <string.h>
using namespace std;
class AA {
public:
int *m_data = nullptr; //数据成员,指向堆区资源的指针
AA() = default; // 启用默认构造函数
void alloc() { // 给数据成员m_data分配内存
m_data = new int; // 分配内存
memset(m_data, 0, sizeof(int)); //初始化已分配的内存
}
AA(const AA &a) { //拷贝构造函数 - 拷贝语义
cout << "调用了拷贝构造函数 。\n"; // 显示自己被调用的日志
if (m_data == nullptr) alloc(); // 如果没有分配内存,就分配
memcpy(m_data, a.m_data, sizeof(int)); //把数据从源对象中拷贝过来
}
AA(AA &&a) { //拷贝构造函数 - 移动语义
cout << "调用了移动语义拷贝构造函数 。\n"; // 显示自己被调用的日志
if (m_data != nullptr) delete m_data; // 如果已经分配内存,先释放
m_data = a.m_data; // 把资源从源对象中转移过来
a.m_data = nullptr; // 把源对象中的指针置空
}
AA &operator=(const AA &a) { //赋值函数 - 拷贝语义
cout << "调用了赋值函数。\n"; // 显示自己被调用的日志
if (this == &a) return *this; // 避免自我赋值
if (m_data == nullptr) alloc(); // 如果没有分配内存,就分配
memcpy(m_data, a.m_data, sizeof(int)); // 把数据从源对象中拷贝过来
return *this;
}
AA &operator=(AA &&a) { //赋值函数 - 移动语义
cout << "调用了移动语义赋值函数。\n"; // 显示自己被调用的日志
if (this == &a) return *this; // 避免自我赋值
if (m_data != nullptr) delete m_data; // 如果已经分配内存,先释放
m_data = a.m_data; // 把资源从源对象中转移过来
a.m_data = nullptr; // 把源对象中的指针置空
return *this;
}
~AA() { // 析构函数
cout << "调用析构函数" << endl;
if (m_data != nullptr) {
delete m_data;
m_data == nullptr;
}
}
};
int main() {
AA a1; // 创建对象a1
a1.alloc(); // 分配堆区资源
*a1.m_data = 3; // 给堆区内存赋值
cout << "*a1.m_data = " << *a1.m_data << ",addr = " << a1.m_data << endl;
AA a2 = a1; // 调用拷贝构造函数 - 这个地方a1是左值就调用拷贝语义构造函数,如果是右值,则调用移动语义构造函数
cout << "*a2.m_data = " << *a2.m_data << ",addr = " << a2.m_data << endl;
AA a3;
a3 = a1; // 调用赋值函数
cout << "*a3.m_data = " << *a3.m_data << ",addr = " << a3.m_data << endl;
auto f = [] { // 返回AA类对象的lambda函数
AA aa;
aa.alloc();
*aa.m_data = 10;
return aa;
};
AA a4 = f(); // lambda函数返回临时对象,是右值,将调用移动构造函数
cout << "*a4.m_data = " << *a4.m_data << ",addr = " << a4.m_data << endl;
AA a6;
a6 = f(); // lambda函数返回临时对象,是右值,将调用移动赋值函数
cout << "*a6.m_data = " << *a6.m_data << ",addr = " << a6.m_data << endl;
return 0;
}