智能指针

一、智能指针的引入

我们知道,栈是系统开辟并且系统进行释放的,而堆是程序员手动开辟,手动释放的。那么如果程序员忘记手动释放就会造成内存泄露,或者由于程序逻辑运行出现异常,导致代码过早返回,没有执行到free或者delete。那么如何避免这种错误呢,所以引入了智能指针(手动开辟,系统回收)

智能指针是怎么防止内存泄露的,如下代码:

void func()
{
    shared_ptr<int> p1(new int(10));
    *p1 = 20;
}

看这个 func 函数,在函数局部使用了 new 开辟了堆内存,此时 func 函数不管是正常运行结束,或者是代码抛出异常,那么在出作用域之前,智能指针对象 p1 会进行析构,在析构函数里面它就会把管理的堆内存给释放掉,有效的防止了内存泄露。

在C++98标准,只有一个智能指针 auto_ptr
在C++11标准,智能指针有unique_ptr、shared_ptr 、weak_ptr 不用auto_ptr(有缺陷)借鉴boost库引用的 

二、智能指针的实现原理

智能指针是一个类,这个类的构造函数中传入一个普通指针,析构函数中释放传入的指针。智能指针的类都是栈上的对象,所以当函数(或程序)结束时,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

什么是智能指针? 智能的管理指针所指向的动态资源的释放。它是一个类,有类似指针的功能。 

三、四种智能指针

<1>   auto_ptr --- 所有权唯一,但所有权可以转移

模拟实现:

#include<iostream>
using namespace std;

//在实现的时候不知道管理什么样的内存块,所以用模板来实现 
template<typename T>
class Auto_Ptr
{
public:
	//构造函数
	Auto_Ptr(T* ptr)
		:mptr(ptr)
	{}

	//拷贝构造
	Auto_Ptr(Auto_Ptr<T>& rhs)
	{
		mptr = rhs.Release();
	}

	//赋值运算符重载
	Auto_Ptr<T>& operator=(Auto_Ptr<T>& rhs)
	{
		if (this != &rhs)//自赋值的判断
		{
			delete mptr;
			mptr = rhs.Release();//转让权限,取消了旧指针的所有权
		}
		return *this;
	}

	//析构函数
	~Auto_Ptr()
	{
		delete mptr;//Auto_Ptr不能用来管理数组
		mptr = NULL;
	}
	//解引用
	T& operator*()
	{
		return *mptr;
	}
	//调用
	T* operator->()
	{
		return mptr;//return (this->mptr);
	}
private:
//这个函数只是把智能指针赋值为空,但是它原来指向的内存并没有被释放,相当于它只是释放了对资源的所有权
	T* Release()//转让权限
	{
		T* tmp = mptr;
		mptr = NULL;
		return tmp;
	}
	T* mptr;
};

 

缺陷:

1.一个指针变量指向的空间不能由两个auto_ptr管理,不然会析构两次,使程序崩溃 --- 所有权唯一,不能共享内存块

//错误
int *ap = new int;
auto_ptr<int> a1(ap);
auto_ptr<int> a2(ap);

2、赋值或拷贝构造将原指针的所有权转移给新指针,会使得原指针悬空,直接编译不会出错,但解引用是会出现很多问题

基于这个原因,应该避免把auto_ptr放到容器中,因为算法对容器操作时,很难避免STL内部对容器实现了赋值传递操作(出错了也不易发现),这样会使容器中很多元素被置为NULL。

auto_ptr<int> ap1(new int);
auto_ptr<int> ap2(ap1);
//写上面两句不会报错,但是如果对旧指针进行操作,运行出错
*ap1 = 20;//出错

ap2剥夺了ap1的所有权,当程序运行时访问ap1将会报错。

3、auto_ptr不能用来管理数组,析构函数中用的是delete

 

<2>   unique_ptr  --- 所有权唯一且不能转移

以将构造和拷贝构造放到私有下,来保证他的所有权唯一,所有权不能能转移

它是( C++11引入的,前身是scoped_ptrscoped_ptrboost库里的),也不支持拷贝构造和赋值,但比auto_ptr好,直接赋值会编译出错(与auto_ptr最大的不同就是类内私有的声明了拷贝构造函数和赋值运算符重载,是针对auto_ptr的缺点而出现的)

模拟实现:

#include<iostream>
using namespace std;

template<typename T>
class Unique_Ptr
{
public:
	Unique_Ptr(T* ptr)
		:mptr(ptr)
	{}

	~Unique_Ptr()
	{
		delete mptr;
		mptr = NULL;
	}
	T& operator*()
	{
		return *mptr;
	}
	T* operator->()
	{
		return mptr;
	}
private:
	//将赋值和拷贝构造写到私有,禁止所有权转移
	Unique_Ptr(Unique_Ptr<T>& _Right);
	Unique_Ptr<T>& operator=(Unique_Ptr<T>& _Right);
	T* mptr;
};

因为构造和拷贝构造放到私有下,所以再进行拷贝构造,编译就会出错,保证了他所以有权唯一

缺陷:

1、所有权唯一,不能数据共享

设计层面保证所有权唯一了 ,但是外部控制也可以让他指向同一块内存--->程序崩溃

//错误
int* p = new int;
unique_ptr<int> up1(p);
unique_ptr<int> up2(p);

<3>   shared_ptr  ---  所有权不唯一,基于引用计数

资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。

图来自:如何回答C++面试中关于智能指针的问题?

shared_ptr是强引用指针 ,会有智能指针相互引用的问题。比如上图的双向链表

解决这种状况的办法就是将两个类中的一个成员变量改为weak_ptr对象,因为weak_ptr不会增加引用计数,使得引用形不成环,最后就可以正常的释放内部的对象,不会造成内存泄漏

<4>   weak_ptr  --- 和shared_ptr搭配使用

 引用计数有一个问题就是互相引用形成环,这样两个指针指向的内存都无法释放。需要手动打破循环引用或使用weak_ptr。weak_ptr是一个弱引用,它是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前需要检查weak_ptr是否为空指针。

weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。

weak_ptr也维护了一个引用计数,跟shared_ptr维护的引用计数或互不干扰,或相互协同。weak_ptr的指针会在weak_ptr维护的引用计数上加一,而shared_ptr会在shared_ptr维护的引用计数上加一,这样在循环引用时,就会因为对不同引用的判断的不同,使最终决定是否释放空间的结果也不相同。

有一个规定,就是创建对象的时候,持有它的强智能指针,当其他地方想使用这个对象的时候,应该持有该对象的弱智能指针,

Q:如何判断weak_ptr的对象是否失效? 
A
1expired():检查被引用的对象是否已删除。 
2
lock()会返回shared指针,判断该指针是否为空。 
3
use_count()也可以得到shared引用的个数,但速度较慢。

(1)weak_ptr虽然是一个模板类,但是不能用来直接定义指向原始指针的对象。
(2)weak_ptr接受shared_ptr类型的变量赋值,但是反过来是行不通的,需要使用lock函数。
(3)weak_ptr设计之初就是为了服务于shared_ptr的,所以不增加引用计数就是它的核心功能。
(4)由于不知道什么之后weak_ptr所指向的对象就会被析构掉,所以使用之前请先使用expired函数检测一下。
 

参考博客:C++智能指针——探究六个常见的智能指针的使用及原理

智能指针 auto_ptr、scoped_ptr、shared_ptr、weak_ptr

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值