移动语义
-
后置++,–不能取地址,返回的是局部对象(右值)
-
const左值引用称为万能引用, 无法区分出绑定的是左值还是右值
-
编译器优化关掉编译优化
-fno-elide-constructors
-
移动语义是为了避免反复构造、析构持有资源的对象
(不严格的来说,左值对应变量的存储位置,而右值对应变量的值本身。C++中右值可以被赋值给左值或者绑定到引用。类的右值是一个临时对象,如果没有被绑定到引用,在表达式结束时就会被废弃。于是我们可以在右值被废弃之前,移走它的资源进行废物利用,从而避免无意义的复制。被移走资源的右值在废弃时已经成为空壳,析构的开销也会降低)
(赋值保持资源不变,而移动会使得源状态改变)
移动构造函数
-
右值引用&&,只能绑定到右值,右值引用在定义时必须初始化
类型 && 引用变量名字 = 实体;
-
不能取地址,没有名字
-
//String s=String("hello,world");不需要再new String (String &&rhs)//比拷贝构造函数更早执行 :_pstr(rhs._pstr) { rhs._pstr=nullptr; } int &&func(){//右值 return 10; } int &&ref=10;//左值
移动赋值运算符函数
-
String &operator=(String &&rhs){//比赋值运算符更早执行 if(this!=&rhs){//自移动,因为有s1=std::move(s1); delete [] _pstr;//释放左操作数 _pstr=nullptr; _pstr=rhs._pstr;//浅拷贝 rhs._pstr=nullptr; } return *this;//返回*this }
std::move
-
可以将左值转化为右值,实际上就是强制转换
static_cast<T &&>(lvalue);
-
std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能
-
std::move()作用于内置类型没有任何作用,内置类型(整型、浮点型)本身是左值还是右值,经过std::move()后不会改变
-
//s1为左值,现转化为右值,左值就不能直接使用了,打印为空,想继续使用就重新赋值,不想销毁而是转给其他 s1 = std::move(s2); s2 = "hello";//new一个之后赋值运算符函数 int num1=10; int num2=20; std::move(num1);//没有资源需要转走,无作用效果 cout<<num1<<endl; cout<<num2<<endl;
移动语义与复制控制语义
- 把移动构造函数与移动赋值运算符函数称为具有移动语义的函数
- 把拷贝构造函数与赋值运算符函数称为具有复制控制语义的函数
- 具有移动语义的函数优先于具有复制控制语义的函数执行
- 具有移动语义的函数编译器不会自动生成,但是具有复制控制语义的函数编译器会自动生成
资源管理
- C中用于释放资源的代码需要在不同的位置重复书写多次
- C++中RAII可以利用栈对象的生命周期管理资源(内存、文件句柄、锁mutex)
RAII
-
一般在资源获取时构造对象,在对象生存期间资源一直有效,对象析构时,资源被释放
-
保证资源的释放顺序与获取顺序严格相反
(构造与析构)
-
对象语义:一般不允许复制或者赋值
(拷贝构造函数与赋值运算符函数设为私有,将拷贝构造函数与赋值运算符函数=delete)
-
值语义:可以进行复制或者赋值
-
template <typename T> class RAII{ public: //在构造的时候初始化资源 RAII(T *data):_data(data){ cout<<"RAII(T *)"<<endl; } //在析构函数中销毁资源 ~RAII(){ cout<<"~RAII(T *)"<<endl; if(_data){ delete _data; _data=nullptr; } } //提供若干访问资源的方法 T *operator->(){ return _data; } T &operator*(){ return *_data; } T *get(){ return _data; } void reset(T *data){ if(_data){ delete _data;//自动调用了Pointx析构 _data=nullptr; } } //不允许复制或者赋值,c++11写法 RAII(const RAII &rhs)=delete; RAII &operator=(const RAII &rhs)=delte; private: T *_data; }; int main(){ RAII<Point> ppt(new Point(10,20));//管理堆空间资源,具有智能指针unique_ptr的功效 ppt->print();//重载-> return 0; }
智能指针
- 头文件memory
- 原生的裸指针int*,char*,非智能指针都叫裸指针
- up、ap对不等号有重载,可以直接if(sp/up)判断是否为空
第1种auto_ptr
- auto_ptr ap();
- 表面上进行了拷贝操作,但是拷贝构造函数中已经进行了所有权的转移,所以该智能指针存在缺陷
- 已弃用
第2种unique_ptr
- unique_ptr up();
- 独享所有权的智能指针,不允许复制或者赋值,但是可以使用移动语义或者临时对象
- 看似是变量实际是传指针所以才可以new来初始化
第3种shared_ptr
-
shared_ptr sp();
(强引用)
-
引用计数use_count的智能指针,用于共享对象的所有权
-
问题:循环引用,引用计数永远大于等于1
第4种weak_ptr
-
weak_ptr wp();
(弱引用)
-
构造函数不能传裸指针,可以传无参、智能指针shared_ptr
-
不会导致引用计数加一,配合shared_ptr解决循环引用的问题
阻塞还是异步?
-
它不能直接获取资源,必须通过lock函数从wp提升为sp,此时原sp引用计数会加一,从而判断共享的资源是否已经销毁
shared_ptr<Point> sp2=wp.lock();
cout<<wp.expired()<<endl;//是否到期,到期为1
if(sp2){
cout<<"提升成功"<<endl;
cout<<"*sp2"<<*sp2<<endl;
}else{
cout<<"提升失败"<<endl;
}
删除器
- 若我们采用malloc申请的空间或是用fopen打开的文件,这时我们的智能指针就无法来处理,因此我们需要为智能指针定制删除器,提供一个可以自由选择析构的接口,这样,我们的智能指针就可以处理不同形式开辟的空间以及可以管理文件指针
- unique_ptr<typename,deletor> up();//类型
- shared_ptr sp(,deletor());//临时对象
struct deletor{
void operator()(FILE *fp){
if(fp)
fclose(fp);
}
};
错误使用
- 同一个裸指针被不同的智能指针托管,导致被析构两次
- 指针进行add时隐含返回了裸指针,被不同智能指针托管
- 改成智能指针似乎可以,但是返回的是不是对象本身,是一个临时对象,返回引用时实体生命周期应大于函数
/临时对象
- 改成智能指针似乎可以,但是返回的是不是对象本身,是一个临时对象,返回引用时实体生命周期应大于函数
struct deletor{
void operator()(FILE *fp){
if(fp)
fclose(fp);
}
};
错误使用
- 同一个裸指针被不同的智能指针托管,导致被析构两次
- 指针进行add时隐含返回了裸指针,被不同智能指针托管
- 改成智能指针似乎可以,但是返回的是不是对象本身,是一个临时对象,返回引用时实体生命周期应大于函数
- 所以可以使用友元(不是自定义的,无法实现)或者公有继承enable_shared_from_this类,来返回shared_from_this();