1.3.1 智能指针

智能指针

为什么要使用智能指针

  1. 内存泄露:内存手动释放,使用智能指针可以自动释放。
    malloc了就要free,new了就要delete,很麻烦。
  • 类内的指针,在析构函数里释放
  • 堆区里c++内置数据类型,没有析构函数怎么办?—手工delete
  • new出来的类,必须要detele才能调用析构函数,又容易忘记
  1. 共享所有权指针的传播和释放,比如多线程使用同一个对象时析构问题
    在这里插入图片描述
    多个只读线程需要访问同一个数据帧,如果全是深拷贝就很浪费资源,如果全是浅拷贝,就会在第一个访问结束后,释放该数据帧,其他线程就访问不到了。
  • 智能指针是类模板对象,在栈上创建智能指针对象
  • 把普通指针交给智能指针对象
  • 智能指针过期时,调用析构函数释放普通指针的内存

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。
提升的行为是线程安全的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值