C++智能指针

本文介绍了内存泄露的危害和类型,重点讲解了C++中智能指针的概念和使用,包括RAII原则,以及std::auto_ptr、std::unique_ptr、std::shared_ptr和std::weak_ptr的原理和应用场景。智能指针通过自动管理内存,有效防止内存泄露,提高程序的健壮性。

智能指针
1.内存泄露
1.1内存泄露的危害

内存泄露指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

1.2内存泄露分类
堆内存泄露
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

系统资源泄露
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

2.智能指针的使用及原理
2.1RAII
RAII是一种利用对象生命周期来控制程序资源的技术。

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。

实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:1.不需要显示地释放资源。2.采用这种方式,对象所需的资源在其生命期内始终保持有效。
在这里插入图片描述
2.2智能指针原理
上述的SmartPtr还不能称为智能指针,因为他还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此还需要将*、->重载才可以像指针一样去使用。
在这里插入图片描述
智能指针的原理就是RAII特性和具有像指针一样的行为。

2.3std::auto_ptr
C++98版本的库中就提供了auto_ptr的智能指针。
在这里插入图片描述
auto_ptr的实现原理:管理权转移的思想。
在这里插入图片描述
2.4std::unique_ptr
C++11中开始提供更靠谱的unique_ptr
在这里插入图片描述
unique_ptr的实现原理:简单粗暴的防拷贝。
在这里插入图片描述
2.5std::shared_ptr
C++11中开始提供更靠谱的并且支持拷贝的shared_ptr
在这里插入图片描述
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间的共享资源。
template
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
,_pcount(new int(1))
,_pmtx(new mutex)
{}

shared_ptr(shared_ptr<T>& sp)
	:_ptr(sp._ptr)
	,_pcount(sp._pcount)
	,_pmtx(sp._pmtx)
{
	add_ref_count();
}

shared_ptr<T>& operator=(shared_ptr<T>& sp)
{
	if (this != &ap)
	{
		release();
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		_pmtx = sp._pmtx;
		add_ref_count();
	}
	return *this;
}

void add_ref_count()
{
	_pmtx->lock();
	++(*_pcount);
	_pmtx->unlock();
}

void release()
{
	bool flag = false;
	_pmtx->lock();
	if (--(*_pcount) == 0)
	{
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
		delete _pcount;
		_pcount = nullptr;
		flag = true;
	}
	_pmtx->unlock();
	if (flag)
	{
		delete _pmtx;
		_pmtx = nullptr;
	}
}

int use_count()
	{
   		return *_pcount;
}

	T* get_ptr() const
{
	return _ptr;
}

T& operator*()
{
	return *_ptr;
}

T* operator->()
{
	return _ptr;
}

~shared_ptr()
{
	release();
}

private:
T* _ptr;
int* _pcount;
mutex* _pmtx
};

std::shared_ptr的循环使用
在这里插入图片描述
a.node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
b.node1的next指向node2,node2的prev指向node1,引用计数变成2。
c.node1和node2析构,引用计数减到1,但是next还指向下一个节点。但是prev还指向上一个节点。
d.也就是说_next析构了,node2就释放了。
e.也就是说_prev析构了,node1就释放了。
f.但是next属于node的成员,node1释放了,next才会析构,而node1由prev管理,prev属于node2成员,所以这就叫循环引用,谁也不会释放。

2.6std::weak_ptr
解决shared_ptr循环使用的方法就是把节点中的prev和next改成weak_ptr就可以了。
在这里插入图片描述
weak_ptr的原理是:不会增加引用计数。
在这里插入图片描述
2.7删除器
对于不是new出来的对象shared_ptr设计了一个删除器来解决这个问题。

在这里插入图片描述
3.RAII拓展
守卫锁
在这里插入图片描述

总结:
为什么要使用智能指针:
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

  1. auto_ptr(c++98的方案,cpp11已经抛弃)
    采用所有权模式。

auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.”));
auto_ptr p2;
p2 = p1; //auto_ptr不会报错.

此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!

  1. unique_ptr(替换auto_ptr)
    unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。
    采用所有权模式,还是上面那个例子

unique_ptr p3 (new string (“auto”)); //#4
unique_ptr p4; //#5
p4 = p3;//此时会报错!!

编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。
另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,比如:

unique_ptr pu1(new string (“hello world”));
unique_ptr pu2;
pu2 = pu1; // #1 not allowed
unique_ptr pu3;
pu3 = unique_ptr(new string (“You”)); // #2 allowed

其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。
注:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。例如:

unique_ptr ps1, ps2;
ps1 = demo(“hello”);
ps2 = move(ps1);
ps1 = demo(“alexia”);
cout << *ps2 << *ps1 << endl;

  1. shared_ptr
    shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
    shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。
    成员函数:
    use_count 返回引用计数的个数
    unique 返回是否是独占所有权( use_count 为 1)
    swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
    reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
    get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr sp(new int(1)); sp 与 sp.get()是等价的
  2. weak_ptr
    weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。
    class B;
    class A
    {
    public:
    shared_ptr pb_;
    ~A()
    {
    cout<<“A delete\n”;
    }
    };
    class B
    {
    public:
    shared_ptr pa_;

~B()
{
cout<<“B delete\n”;
}
};
void fun()
{

shared_ptr pb(new B());
shared_ptr pa(new A());
pb->pa_ = pa;
pa->pb_ = pb;
cout<<pb.use_count()<<endl;
cout<<pa.use_count()<<endl;
}
int main()
{
fun();
return 0;
}
可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A B的析构函数没有被调用),如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_; 改为weak_ptr pb_; 运行结果如下,这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减一,同时pa析构时使A的计数减一,那么A的计数为0,A得到释放。
注意的是我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(); 英文pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值