C++11:智能指针 unique_ptr、shared_ptr、weak_ptr 介绍

智能指针

  • 内存泄漏
    • 抛异常问题
  • 智能指针模拟实现
    • RAII 思想
    • 解决抛异常问题
    • 实现智能指针的解引用
    • 智能指针的拷贝问题
  • 智能指针发展过程
  • unique_ptr
  • shared_ptr
    • 引用计数解决拷贝问题
    • 引用计数多线程安全问题
    • shared_ptr 管理的对象的线程安全问题
    • 定制删除器
      • 模拟实现 shared_ptr 支持定制删除器
  • 循环引用计数问题
  • weak_ptr
    • 模拟实现 weak_ptr

内存泄漏

内存泄漏:因为疏忽或错误造成程序未能释放已经不再使用的内存的情况

内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的分类(两种):

  • 堆内存泄漏:程序在执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存。但是在使用完这些内存后,没有对这内存进行释放,造成这部分空间将无法再被使用

  • 系统资源泄漏:指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉

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

抛异常问题

面对内存泄漏问题,有小伙伴就会说:只要我养成良好的编码规范,申请的内存空间记着匹配的去释放,这样不就好了吗。

良好的编码习惯是没错,但是一般的工程文件很大,代码很多。即使当下申请了资源,但是编写了一大段代码后,也难免会忘记。就当一个人记忆力很好,每次都能记得自己申请的资源,都去释放了对应的资源。但是,面对异常问题又该如何去解决呢?

来看看这样的例子:

int div()
{
   
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
   
	int* p1 = new int;
	int* p2 = new int;

	cout << div() << endl;

	delete p1;
	delete p2;
}

int main()
{
   
	try
	{
   
		Func();
	}
	catch (exception& e)
	{
   
		cout << e.what() << endl;
	}

	return 0;
}

下面简单分析一下代码:

  1. 先来看看 Func 函数:p1、p2在堆区申请了两个 int 大小的内存。在不考虑申请资源失败的情况下,接下来就是调用 div() 函数,并且输出打印的内容。之后就是对 p1、p2 申请资源的释放。
  2. div 函数实现的是除法功能。但是,这里得考虑一个除数不能为零问题。当用户输入的除数为零时,直接进行抛异常

问题来了,异常被抛出,执行的程序就会直接跳转到 catch 来捕获异常,不再按照顺序执行。此刻,p1、p2 申请的资源没有释放,直接引发了内存泄漏问题。

当然,抛异常也可以处理:div函数 抛的异常可以在 Func函数内部就直接进行捕获,处理完 p1、p2资源后再将异常抛出到最外层 main 函数即可:

int div()
{
   
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
   
	int* p1 = new int;
	int* p2 = new int;

	try
	{
   
		cout << div() << endl;
	}
	catch (...)
	{
   
		delete p1;
		delete p2;
		throw; //再次抛出异常
	}
}

int main()
{
   
	try
	{
   
		Func();
	}
	catch (exception& e)
	{
   
		cout << e.what() << endl;
	}

	return 0;
}

如果 p1、p2申请资源失败了是不是也要进行抛异常处理,处理后的代码会写成下面这样:

int div()
{
   
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
   
	int* p1 = new int;//p1申请资源失败,不需要处理的其他资源

	int* p2 = nullptr;

	try
	{
   
		p2 = new int; 
	}
	catch (...)
	{
   
		delete p1;
		throw;//p2申请资源失败,释放p1申请的资源,再抛出异常
	}
	
	try
	{
   
		cout << div() << endl;
	}
	catch (...)
	{
   

		delete p1;
		delete p2;
		throw;//div函数执行错误,释放p1、p2申请的资源,再抛出异常
	}
}

这里还只是处理两个资源释放的问题,在一个项目中存在着大量的资源申请与释放的操作,难道抛异常都这样去实现吗?

这样操作是不现实的,很繁琐很麻烦!为了解决类似 C++ 抛异常的问题,提出了智能指针方案。

智能指针模拟实现

开始前,来了解一下 RAII 思想:

RAII 思想

RAII 是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

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

使用 RAII 思想来实现对资源控制的好处:

  1. 不需要显式地释放资源
  2. 采用这种方式,对象所需的资源在其生命期内始终保持有效

RAII 是一种思想,智能指针就是在这样的思想下构建出来的。

接下来,小编就按着 RAII 的思想来模拟实现一个简单版本的智能指针:

定义一个 SmartPtr 模板类,内部只包含一个指针成员变量。构造函数完成对资源的申请、析构函数实现对资源的释放:

template<typename T>
class smartPoint
{
   
public:
	//构造
	smartPoint(T* ptr)
		:_ptr(ptr)
	{
   
		std::cout << "smartPoint():new _ptr" << std::endl;
	}
	//析构
	~smartPoint()
	{
   
		std::cout << "~smartPoint():delete _ptr" << std::endl;
		delete _ptr;
	}
private:
	T* _ptr;
};

一个对象的生命周期是从实例化对象开始,到这个对象所处的函数生命周期为结束

int main()
{
   
	smartPoint<int> p1(new int);
	return 0;
}

C++11:智能指针 unique_ptr、shared_ptr、weak_ptr 介绍_第1张图片

解决抛异常问题

有了智能指针的这套玩法,在抛异常那我们还需要焦头烂额吗?利用RAII思想就可以很好解决抛异常问题:

int div()
{
   
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}

void Func()
{
   
	smartPoint<int> p1(new int);
	smartPoint<int> p2(new int);
	
	cout << div() << endl;
}
int main()
{
   
	try
	{
   
		Func();
	}
	catch (exception& e)
	{
   
		cout << e.what() << endl;
	}

	return 0;
}

C++11:智能指针 unique_ptr、shared_ptr、weak_ptr 介绍_第2张图片
异常随便你怎么抛,程序执行跳转前都要先对栈帧资源先清空,对此,智能指针能够很好解决资源的释放问题。

接下来完善一下 SmartPtr 类:

实现智能指针的解引用

智能指针也是指针,如果想要像指针那般访问到资源数据,就要在类中实现对应的 operator*()operator->()
( operator->() 常用于自定义类型的数据)

template<typename T>
class smartPoint
{
   
public:
	//构造
	smartPoint(T* ptr)
		:_ptr(ptr)
	{
   
		std::cout << "smartPoint():new _ptr" << std::endl;
	}
	//析构
	~smartPoint()
	{
   
		std::cout << "~smartPoint():delete _ptr" << std::endl;
		delete _ptr;
	}
	
	//解引用获取数据资源
	T& operator*()
	{
   
		return *_ptr;
	}
	//->用于自定义类型
	T* operator->()
	{
   
		return _ptr;
	}
private:
	T* _ptr;
};

实例化一个 smartPoint 对象,输出对应的数据:

#include 
using namespace std;

int main()
{
   
	//RAII:利用对象的生命周期来管理申请的资源
	smartPoint<<

你可能感兴趣的:(c++,开发语言,c语言)