> 文章列表 > c++积累8-右值引用、移动语义

c++积累8-右值引用、移动语义

c++积累8-右值引用、移动语义

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是右值,给它起个名字叫aint b = 8; // b 是左值, 8是右值int &&c = b + 5; // b+5是右值,给它取个名字叫cAA &&aa = getTemp();// getTemp()返回值是右值(临时变量),给它起个名字叫aacout << "a= " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;cout << "aa.m_a= " << aa.m_a << endl;return 0;
}

c++积累8-右值引用、移动语义

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; // 创建对象a1a1.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;
}

c++积累8-右值引用、移动语义

2.3、说明

1 std::move() 左值转换为右值
对于一个左值,会调用拷贝构造函数,但是有些左值是局部变量,声明周期也很短,我们也想使用移动,C++为了解决这种问题,提供了std::move()方法来将左值转义为右值,从而方便使用移动语义。
左值对象被转移资源后,不会立刻析构,只有在离开自己的作用域的时候才会析构,如果继续使用左值的资源,可能会发生意想不到的错误。
2 没有提供移动构造、赋值函数,使用拷贝构造、赋值函数
如果没有提供移动构造/赋值函数,只提供了拷贝构造/赋值函数,编译器找不到移动构造/赋值函数就会去寻找拷贝构造/赋值函数
3 c++11中的所有容器都实现了移动语义,避免对含有资源的对象发生无畏的拷贝
4 移动语义对于拥有资源(如内存、文件句柄)的对象有效,如果是基本类型,使用移动语义没有意义

3、完美转发 std::forward()

如果模版中(包含类模版和函数模版)函数的参数写成 T&& 参数名, 那么函数既可以接收左值引用,右可以接受右值引用。

模版函数:std::forward(参数)用于转发参数。如果参数是一个右值,转发之后仍是右值引用,如果参数是一个左值,转发之后仍是左值引用。

#include <iostream>using namespace std;
void func1(int &i ){cout<< "参数是左值"<< i << endl;
}void func1(int && i){cout << "参数是右值" << i << endl;
}//template<typename T>
//void func2(T &i){
//    cout << "func2 1111" << endl;
//    func1(i);
//}
//
//template<typename T>
//void func2(T&& i){
//    cout << "func2 2222" << endl;
//    func1(move(i));
//}template<typename T>
void func2(T&& i){func1(forward<T>(i));
}//void func2(int& i){
//    func1(i);
//}
//
//void func2(int&&i ){
//    func1(move(i));
//}int main(void) {int a = 3;func2(a);func2(9);
}

std::forward