智能指针
为什么要使用智能指针
- 内存泄露:内存手动释放,使用智能指针可以自动释放。
malloc了就要free,new了就要delete,很麻烦。
- 类内的指针,在析构函数里释放
- 堆区里c++内置数据类型,没有析构函数怎么办?—手工delete
- new出来的类,必须要detele才能调用析构函数,又容易忘记
- 共享所有权指针的传播和释放,比如多线程使用同一个对象时析构问题
多个只读线程需要访问同一个数据帧,如果全是深拷贝就很浪费资源,如果全是浅拷贝,就会在第一个访问结束后,释放该数据帧,其他线程就访问不到了。
- 智能指针是类模板对象,在栈上创建智能指针对象
- 把普通指针交给智能指针对象
- 智能指针过期时,调用析构函数释放普通指针的内存
unique_ptr
定义:意思是unique_ptr独享它指向的对象,也就是说同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁时,它指向的对象也随即被销毁
包含头文件:include
#include<iostream>
#include<vector>
#include<memory>
using namespace std;
class AA {
public:
string m_name;
AA() { cout << m_name << "调用构造函数AA()" << endl; }
AA(const string &name):m_name(name) { cout << "调用构造函数AA("<<m_name<<")" << endl; }
~AA() { cout << "调用析构函数AA(" << m_name << ")" << endl; }
};
int main() {
AA* p = new AA("西施");
//创建AA类型的智能指针pu1来管理p
unique_ptr<AA> pu1(p);
//delete p;
}
即使不使用delete来删除p指针,智能指针也会调用析构函数
原因是:智能指针是类,它有析构函数
初始化方法一:unique_ptr<AA> p0(new AA("西施"));
初始化方法二: unique_ptr<AA> p0 = make_unique<AA>("西施");
初始化方法三:AA* p = new AA("西施"); unique_ptr<AA> p0(p);
1)使用细节
- 智能指针重载了*和->操作符,可以和使用指针一样使用*和->
- 不支持普通的拷贝和赋值
- 不要用同一个裸指针初始化多个unique_ptr对象(所以初始化方法一比初始化方法三更安全)
int main() {
AA* p = new AA("西施");
unique_ptr<AA> pu(p);
cout << " 裸指针的值是:" << p << endl;
cout << " pu的输出结果是:" << pu << endl;
cout << " pu.get()的输出结果是:" << pu.get() << endl;
cout << " pu的地址值是:" << &pu << endl;
}
2)用于函数的参数
- 传引用
void func(unique_ptr<AA> pp) {
cout << "name = " << pp->m_name << endl;
}
int main() {
AA* p = new AA("西施");
unique_ptr<AA> pu(p);
func(pu);
return 0;
}
func(pu); 报错------因为智能指针拷贝构造函数已经被删除
改为传引用------在void func(unique_ptr<AA> pp)
中的pp前面
加上&即可 void func(unique_ptr<AA> &pp)
- 裸指针
- 不支持指针的运算(+,-,++,–)
3)更多技巧
1) 将一个unique_ptr 赋给另一个时,如果原unique_ptr是一个临时右值,编译器允许这样做(即使本来operator=是被禁掉了的),但如果是要存在一段时间就不行,一般是作为函数返回值。
2)用nullptr给unique_ptr赋值将释放对象,空的unique_ptr == nullptr.
int main() {
unique_ptr<AA> pu(new AA("西施"));
if (pu != nullptr) cout << "pu非空" << endl;
pu = nullptr;
if (pu == nullptr) cout << "pu为空" << endl;
return 0;
}
3)release()释放对原始指针的操控权,将unique_ptr置为空,返回裸指针。
4)std::move()可以转移对原始指针的控制权。(可以用于把unique_ptr传递给子函数,子函数形参也是unique_ptr)
测试代码一:
void func1(const AA* a) { // func1需要一个指针,但不对该指针负责
cout <<a->m_name << endl;
}
int main() {
unique_ptr<AA> pu(new AA("西施"));
func1(pu.get());//构造->西施->析构
return 0;
}
测试代码二:
void func2(AA * a) { // func2需要一个指针,并且对该指针负责
cout <<a->m_name << endl;
delete a;
}
int main() {
unique_ptr<AA> pu(new AA("西施"));
func2(pu.release());// 此时就释放了原指针的控制权,并且自己变成空
return 0;
}
测试代码三:
void func3(const unique_ptr<AA> &a) { // func3需要一个unique_ptr,不会对unique_ptr负责
cout <<a->m_name << endl;
}
int main() {
unique_ptr<AA> pu(new AA("西施"));
func3(pu);
return 0;
}
测试代码四:
void func4(const unique_ptr<AA> a) { // func4需要一个unique_ptr,并且会对unique_ptr负责
//使用传值的方法,而不是传引用
cout <<a->m_name << endl;
}
int main() {
unique_ptr<AA> pu(new AA("西施"));
func4(move(pu));//使用move将控制权递交
return 0;
}
5)swap()交换两个unique_ptr的控制权
void swap(unique_ptr<T> &_Right);
6)unique_ptr和普通指针一样具有多态的特性
7)智能指针也不是绝对安全,如果程序中调用exit()退出,全局的unique_ptr可以自动释放,但局部的unique_ptr无法释放;
unique_ptr<AA> pu1(new AA("全局"));
int main() {
unique_ptr<AA> pu2(new AA("局部"));
return 0;//换成exit(0);
}
8)提供了支持数组的具体化版本。
数组版本的unique_ptr,重载了操作符[ ],[ ]返回的是引用,可以作为左值使用。
int main() {
unique_ptr<AA[]> parrs(new AA[2]);
parrs[0].m_name = "西施";
parrs[1].m_name = "貂蝉";
cout << "parrs[0].m_name = " << parrs[0].m_name << endl;
cout << "parrs[1].m_name = " << parrs[1].m_name << endl;
}
shared_ptr
定义:shared_ptr共享它指向的对象,多个shared_ptr可以指向(关联)相同的对象,内部采用计数器机制实现。
当新的shared_ptr与对象关联时,计数器+1
当shared_ptr超过作用域时,计数器-1,当计数器为0时,表示没有任何shared_ptr与对象关联,则释放该对象。
shared_ptr并没有删除拷贝构造函数和赋值函数,因为它需要。
1)初始化
方法一:
shared_ptr<AA> p0(new AA("西施"));
//分配内存并初始化
方法二:
shared_ptr<AA> p0 = make_shared<AA>("西施");
方法三:
AA* p = new AA("西施"); shared_ptr<AA> p0(p);
方法四:
shared_ptr<AA> p0(new AA("西施"));
shared_ptr<AA> p1(p0); //使用构造函数
shared_ptr<AA> p2 = p0;//使用赋值函数
2)使用方法
- use_count()返回计数器的值
- unique()判断计数器的值是不是1,是返回true,不是返回false
- get()返回裸指针
- 不要用同一个裸指针初始化多个shared_ptr
- 不要用shared_ptr管理不是new分配的内存
3)用于函数的参数
基本上和unique_ptr一样
4)不支持指针的运算(+,-,++,–)
5)线程安全性
- shared_ptr的计数器本身是线程安全的(计数是原子操作)
- 多个线程同时读同一个shared_ptr对象是线程安全的
- 如果多个线程同时对同一个shared_ptr对象进行读和写,需要加锁
- 多个线程读写多个shared_ptr对象,如果这些shared_ptr对象指向的是同一个对象,那么也需要加锁
6)效率比较
如果unique_ptr能解决问题,就不要用shared_ptr,unique_ptr的效率更高,需要的资源更少。
7)智能指针的删除器
默认情况下,智能指针过期时,使用delete原始指针,释放它管理的资源
程序员可以自定义删除器,改变智能指针释放资源的行为
删除器可以是全局函数,仿函数,Lamda表达式,形参为原始指针
- 普通函数
void deletefunc(AA* a) {
cout << "自定义删除器(全局函数)" << endl;
delete a;
}
int main() {
shared_ptr<AA> p0(new AA("西施"),deletefunc);
}
- 仿函数
struct deleteclass {
void operator()(AA *a) {
cout << "自定义删除器(仿函数)" << endl;
delete a;
}
};
int main() {
shared_ptr<AA> p0(new AA("西施"),deleteclass());
}
- Lambda表达式
auto deleterlamb = [](AA* a) {
cout << "自定义删除器(Lambda)" << endl;
delete a;
};
int main() {
shared_ptr<AA> p0(new AA("西施"),deleterlamb);
}
- unique_ptr的删除器与shared_ptr删除器的区别
unique_ptr<AA,void(*)(AA*)> p0(new AA("西施"), deleterlamb);
weak_ptr
shared_ptr内部维护了一个共享的计数器,多个shared_ptr可以指向同一个资源。但是,如果出现了循环引用的情况,计数器无法归0,资源就不会被释放。
weak_ptr持有但不拥有对象的所有权,通常与shared_ptr一起使用,以避免循环引用导致的内存泄漏问题。
#include<iostream>
#include<vector>
#include<memory>
using namespace std;
class BB;
class AA {
public:
string m_name;
AA() { cout << m_name << "调用构造函数AA()" << endl; }
AA(const string &name):m_name(name) { cout << "调用构造函数AA("<<m_name<<")" << endl; }
~AA() { cout << "调用析构函数AA(" << m_name << ")" << endl; }
shared_ptr<BB> m_p;
};
class BB {
public:
string m_name;
BB() { cout << m_name << "调用构造函数BB()" << endl; }
BB(const string& name) :m_name(name) { cout << "调用构造函数BB(" << m_name << ")" << endl; }
~BB() { cout << "调用析构函数BB(" << m_name << ")" << endl; }
shared_ptr<AA> m_p;
};
int main() {
shared_ptr<AA> pa = make_shared<AA>("西施a");
shared_ptr<BB> pb = make_shared<BB>("西施a");
pa->m_p = pb;
pb->m_p = pa;
}
结果并不会释放内存(调用析构函数)
将其中一个shared_ptr改为weak_ptr就可以打破循环。
//将classAA中的
shared_ptr<BB> m_p;
//改成
weak_ptr<BB> m_p;
expired函数的用法
expired:判断当前weak_ptr智能指针是否还有托管的对象,有则返回false,无则返回true
如果返回true,等价于 use_count() == 0,即已经没有托管的对象了;当然,可能还有析构函数进行释放内存,但此对象的析构已经临近(或可能已发生)。
重点!!!
weak_ptr不控制对象的生命周期,但它知道对象是否活着。
用lock()函数把它可以提升为shared_ptr,如果对象还活着,返回有效的shared_ptr, 如果对象已经死了,提升会失败,返回一个空的shared_ptr。
提升的行为是线程安全的。