您现在的位置是:首页 >技术交流 >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是右值,给它起个名字叫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 << "调用了拷贝构造函数 。
"; // 显示自己被调用的日志
if (m_data == nullptr) alloc(); // 如果没有分配内存,就分配
memcpy(m_data, a.m_data, sizeof(int)); //把数据从源对象中拷贝过来
}
AA(AA &&a) { //拷贝构造函数 - 移动语义
cout << "调用了移动语义拷贝构造函数 。
"; // 显示自己被调用的日志
if (m_data != nullptr) delete m_data; // 如果已经分配内存,先释放
m_data = a.m_data; // 把资源从源对象中转移过来
a.m_data = nullptr; // 把源对象中的指针置空
}
AA &operator=(const AA &a) { //赋值函数 - 拷贝语义
cout << "调用了赋值函数。
"; // 显示自己被调用的日志
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 << "调用了移动语义赋值函数。
"; // 显示自己被调用的日志
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;
}
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