【C++】智能指针

文章目录

  • 1、为什么需要智能指针?
  • 2、内存泄漏
    • 2-1、什么是内存泄漏,内存泄漏的危害
    • 2-2、内存泄漏分类(了解)
    • 2-3、如何检测内存泄漏(了解)
    • 2-4、如何避免内存泄漏
  • 3、智能指针的使用及原理
    • 3-1、RAII
    • 3-2、智能指针的原理
    • 3-3、std::auto_ptr
    • 3-4、std::unique_ptr
    • 3-5、std::shared_ptr
      • 3-5-1、线程安全问题
      • 3-5-2、循环引用
        • 3-5-2-1、见见猪跑
        • 3-5-2-2、循环引用分析
        • 3-5-2-3、weak_ptr
    • 3-6、定制删除器
  • 4、C++11和boost中智能指针的关系
  • 5、总结


1、为什么需要智能指针?

下面我们先分析一下下面这段程序有没有什么内存方面的问题?

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

void Func()
{
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?

	int* p1 = new int[10];//p1抛异常main函数里面的catch (exception& e)会捕获,没什么问题
	int* p2 = nullptr;
	try
	{
		p2 = new int[20];//p2抛异常问题就大了,这样p1的堆空间没有释放,发生了内存泄漏
							  //所以p2这里还要加上一层try catch
		try//之前的写法
		{
			cout << div() << endl;
		}
		catch (...)
		{
			delete[] p1;
			delete[] p2;
			throw;
		}
	}
	catch (...)//p2的catch还有处理,因为上面div()抛异常捕获后继续向下处理,那么这里的catch也可能被执行
	//所以要继续处理
	{
		//...
	}
	delete[] p1;
	delete[] p2;
}

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

如果还有p3,p4…
为了解决上面复杂等场景,智能指针就产生了

template<class T>
class SmartPtr
{
public:
	SmartPtr(T *ptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		delete []_ptr;
		cout << "delete ptr" << endl;
	}
private:
	T* _ptr;
};

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

void Func()
{
	int* p1 = new int[10];//p1和p2是内置类型,出了作用域没有影响
	SmartPtr<int> sp1(p1);//sp1和sp2是自定义类型,出了作用域要销毁,调用自己的析构函数
	int* p2 = new int[10];
	SmartPtr<int> sp2(p2);
	cout << div() << endl;

	//p1抛异常  ——  下面main函数的catch (exception& e)会捕获,p2就不会产生
	//p2抛异常  ——  sp1出了作用域会调用析构,把p1带走
	//div()抛异常  ——  sp1,sp2出了作用域会调用析构,把p1,p2带走

	SmartPtr<int> sp1(new int[10]);//这样也可以
	SmartPtr<int> sp2(new int[10]);
	cout << div() << endl;
}

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

【C++】智能指针_第1张图片

完整代码:

template<class T>
class SmartPtr
{
public:
	
	///RAII —— 资源获得即初始化
	SmartPtr(T *ptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		delete []_ptr;
		cout << "delete ptr" << endl;
	}
	///


	///像指针一样
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	T& operator[](size_t pos)
	{
		return _ptr[pos];
	}
private:
	T* _ptr;
};

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

void Func()
{
	SmartPtr<int> sp1(new int[10]);
	SmartPtr<int> sp2(new int[10]);
	*sp1 = 10;
	sp1[0]--;
	cout << *sp1 << endl;
	cout << div() << endl;
}

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

RAII —— 资源获得即初始化

这里的RAII下面会讲

2、内存泄漏

2-1、什么是内存泄漏,内存泄漏的危害

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

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

void MemoryLeaks()
{
   // 1.内存申请了忘记释放
  int* p1 = (int*)malloc(sizeof(int));
  int* p2 = new int;
  // 2.异常安全问题
  int* p3 = new int[10];
  Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
  delete[] p3;
}

2-2、内存泄漏分类(了解)

C/C++程序中一般我们关心两种方面的内存泄漏:

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

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

2-3、如何检测内存泄漏(了解)

在linux下内存泄漏检测:linux下几款内存泄漏检测工具
在windows下使用第三方工具:VLD工具说明
其他工具:内存泄漏工具比较

2-4、如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
  1. 采用RAII思想或者智能指针来管理资源。
  1. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  1. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

总结一下:
内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。

3、智能指针的使用及原理

3-1、RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
RAII —— 资源获得即初始化。使资源的生命周期和对象的生命周期绑定了,对象在,资源就在,对象析构销毁了,资源也就跟着销毁了

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

借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
1、不需要显式地释放资源。
2、采用这种方式,对象所需的资源在其生命期内始终保持有效。

3-2、智能指针的原理

上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:

AutoPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用

总结一下智能指针的原理:

  1. RAII特性
  2. 重载operator*和opertaor->,具有像指针一样的行为
// C++智能指针发展历史
// C++98 auto_ptr 资源管理权转移-->对象悬空   很多公司明确要求不能使用它

// boost scoped_ptr 防拷贝
// boost shared_ptr/weak_ptr  // 引用计数

// C++11 unique_ptr 防拷贝(抄的boost scoped_ptr作业)
// C++11 shared_ptr/weak_ptr // 引用计数

3-3、std::auto_ptr

auto_ptr文档

C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。
auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份auto_ptr来了解它的原理

int main()
{
	//SmartPtr sp1(new int);
	//SmartPtr sp2(sp1);//上面没有写拷贝构造,发生了两次析构
	//SmartPtr sp3 = sp1;

	auto_ptr<int> ap1(new int);
	auto_ptr<int> ap2(ap1);//这里资源转移了,ap1为空了!
}

【C++】智能指针_第2张图片
这危害是很大的,产生了对象悬空的问题:
【C++】智能指针_第3张图片

SmartPtr.h

namespace bzh
{
	template<class T>
	class auto_ptr
	{
	public:
		// RAII
		// 保存资源
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}
		// 释放资源
		~auto_ptr()
		{
			//delete[] _ptr;
			delete _ptr;
			cout << _ptr << endl;
		}
		auto_ptr(auto_ptr<T>& sp)//不能加const
			:_ptr(sp._ptr)
		{
			sp._ptr = nullptr;//把原来的指针悬空
		}
		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
	};
}

【C++】智能指针_第4张图片
我们自己的模拟代码也发生了对象悬空的问题,所以98年的智能指针auto_ptr很多公司/项目严禁使用!

boost简单来说就是C++11标准库的探路者,先在boost库里面研究新语法,各方面觉得不错,使用者也觉得还行,就可以改进/封装一下,直接拿到C++标准库里面去
【C++】智能指针_第5张图片

3-4、std::unique_ptr

C++11中开始提供更靠谱的unique_ptr
unique_ptr文档

unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理

namespace bzh
{
	template<class T>
	class unique_ptr
	{
	public:
		// RAII
		// 保存资源
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		// 释放资源
		~unique_ptr()
		{
			//delete[] _ptr;
			delete _ptr;
			cout << _ptr << endl;
		}
		unique_ptr(const unique_ptr<T>& up) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
	};
}

【C++】智能指针_第6张图片
这里unique_ptr就简单得多了,直接防拷贝,把路封死
但是你不让拷贝也不是事,所以下面的智能指针就出来了

3-5、std::shared_ptr

C++11中开始提供更靠谱的并且支持拷贝的shared_ptr

shared_ptr文档

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

例如:老师晚上在下班之前都会通知,让最后走的学生记得把门锁下。

1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。

2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。

3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;

4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了

【C++】智能指针_第7张图片

每个指针指向两个地方,一个是资源,一个是引用计数

namespace bzh
{
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		// 保存资源
		shared_ptr(T* ptr = nullptr)//默认构造
			:_ptr(ptr)
			, _pcount(new int(1))//初始值就要为1
		{}
		// 释放资源
		~shared_ptr()
		{
			Release();
		}
		shared_ptr(const shared_ptr<T>& sp)//拷贝构造,浅拷贝
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}
		void Release()
		{
			if (--(*_pcount) == 0)
			{
				delete _pcount;
				delete _ptr;
			}
		}

		// sp1 = sp1;
		// sp1 = sp2;
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				Release();//先判断计数,减了计数等于0,就表示是最后一个指针,就要释放了

				_pcount = sp._pcount;
				_ptr = sp._ptr;
				++(*_pcount);//*_pcount解引用拿到引用计数值
			}
			return *this;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
		int* _pcount;//堆区开辟引用计数
	};
}

【C++】智能指针_第8张图片

【C++】智能指针_第9张图片

3-5-1、线程安全问题

通过下面的程序我们来测试shared_ptr的线程安全问题。需要注意的是shared_ptr的线程安全分为两方面:

  1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是线程安全的。
  1. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题
void test_shared_ptr()//两个线程对智能指针sp1疯狂拷贝,析构
{
	int n = 10000;
	shared_ptr<int> sp1(new int(1));
	thread t1([&]()
	{
		for (size_t i = 0; i < n; ++i)
		{
			shared_ptr<int> sp2(sp1);

		}
	});
	thread t2([&]()
	{
		for (size_t i = 0; i < n; ++i)
		{
			shared_ptr<int> sp3(sp1);
		}
	});
	t1.join();
	t2.join();

	cout << sp1.use_count() << endl;
	cout << sp1.get() << endl;
}

【C++】智能指针_第10张图片
引用计数大于1,表示我们少 - - 了,小于1表示我们多 - -了
那么,为了防止这种严重事故的发生,我们在使用多线程/多进程(linux中没有线程这个概念,而我们常说的一个线程在llinux中是指:一个轻量级进程)

但是,我们在使用锁的时候,不能直接在结构体里面添加一个锁的变量,因为我们的目的是++和 – 操作到同一个锁上面,当sp2和sp3拷贝sp1的时候,就不是一把锁了,而是三把,所以不能直接在结构体里面直接添加锁
而正确的使用锁的方法和前面我们使用引用计数一样,堆区上面开辟一个锁的空间,指针指向锁;这样,每一个智能指针就指向3个东西:资源空间,引用计数,锁

【C++】智能指针_第11张图片

namespace bzh
{
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		// 保存资源
		shared_ptr(T* ptr = nullptr)//默认构造
			:_ptr(ptr)
			, _pcount(new int(1))//初始值就要为1
			,_pmtx(new mutex)
		{}
		// 释放资源
		~shared_ptr()
		{
			Release();
		}
		shared_ptr(const shared_ptr<T>& sp)//拷贝构造,浅拷贝
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			,_pmtx(sp._pmtx)//这个时候拷贝,就是拷贝锁的指针了
		{
			_pmtx->lock();//加锁 —— 多个线程同时执行++操作时,只有一个线程能够执行成功
			++(*_pcount);
			_pmtx->unlock();//解锁

		}
		void Release()
		{
			bool flag = false;//flag是局部变量,有独立的栈结构,互相不影响
			_pmtx->lock();
			if (--(*_pcount) == 0)
			{
				delete _pcount;//这些变量都是堆上面的,堆区的数据是共享的
				delete _ptr;
				flag = true;
			}
			_pmtx->unlock();
			if(flag == true)
				delete _pmtx;

		}

		// sp1 = sp1;
		// sp1 = sp2;
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				Release();//先判断计数,减了计数等于0,就表示是最后一个指针,就要释放了

				_pcount = sp._pcount;
				_ptr = sp._ptr;
				_pmtx = sp._pmtx;

				_pmtx->lock();
				++(*_pcount);
				_pmtx->unlock();

			}
			return *this;
		}

		int use_count()
		{
			return *_pcount;
		}

		T* get()const
		{
			return _ptr;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;/这里的锁只保护引用计数的安全,智能指针指向的资源空间安全不保护!!
		int* _pcount;//堆区开辟引用计数
		mutex* _pmtx;//堆区开辟锁空间
	};

	void test_shared_ptr()//两个线程对智能指针sp1疯狂拷贝,析构
	{
		int n = 10000;
		shared_ptr<int> sp1(new int(1));
		thread t1([&]()
		{
			for (size_t i = 0; i < n; ++i)
			{
				shared_ptr<int> sp2(sp1);

			}
		});
		thread t2([&]()
		{
			for (size_t i = 0; i < n; ++i)
			{
				shared_ptr<int> sp3(sp1);
			}
		});
		t1.join();
		t2.join();

		cout << sp1.use_count() << endl;
		cout << sp1.get() << endl;
	}
}

【C++】智能指针_第12张图片

锁只保护引用计数,不保护智能指针指向的资源空间:

struct Date
		{
			int _year = 0;
			int _month = 0;
			int _day = 0;
		};

		void test_shared_ptr()
		{
			int n = 10000;
			mutex mtx;
			shared_ptr<Date> sp1(new Date);
			thread t1([&]()//&捕捉到了mtx
				{
					for (size_t i = 0; i < n; ++i)
					{
						shared_ptr<Date> sp2(sp1);
						mtx.lock();Date结构里面的资源++/--不是线程安全的,要加锁!!!
						sp2->_day++;
						sp2->_month++;
						sp2->_year++;
						mtx.unlock();
					}
				});
			thread t2([&]()//&捕捉到了mtx
				{
					for (size_t i = 0; i < n; ++i)
					{
						shared_ptr<Date> sp3(sp1);
						mtx.lock();
						sp3->_day++;
						sp3->_month++;
						sp3->_year++;
						mtx.unlock();
					}
				});
			t1.join();
			t2.join();

			cout << sp1.use_count() << endl;
			cout << sp1.get() << endl;
			cout << sp1->_year << endl;
			cout << sp1->_month << endl;
			cout << sp1->_day << endl;
	}

shared_ptr 本身是线程安全的。(拷贝和析构时,引用计数++ --是线程安全的)
shared_ptr 管理资源的访问不是线程安全的,需要用的地方自行保护

3-5-2、循环引用

3-5-2-1、见见猪跑

	struct ListNode以前的玩法
	{
		ListNode* _next;
		ListNode* _prve;
		int _val;
		~ListNode()
		{
			cout << "delete node" << endl;
		}
	};
	void test_shared_ptr2()//以前的玩法
	{
		ListNode* n1 = new ListNode;
		ListNode* n2 = new ListNode;
		//...
		n1->_next = n2;
		n2->_prve = n1;
		//...
		delete n1;//最终释放n1和n2
		delete n2;
	}

改为智能指针之后:

struct ListNode
	{
		ListNode* _next;
		ListNode* _prve;
		int _val;
		~ListNode()
		{
			cout << "delete node" << endl;
		}
	};
	void test_shared_ptr2()//智能指针的改法,不用自己delete
	{
		shared_ptr <ListNode> n1 = new ListNode;
		shared_ptr <ListNode> n2 = new ListNode;

		//n1->_next = n2;//这里改着改出问题了
		//n2->_prve = n1;//_next和_prve是原生指针,而n1和n2是智能指针,智能指针不能赋值给原生指针!!!

		//delete n1;
		//delete n2;
	}

继续改进:

struct ListNode
	{
		std::shared_ptr<ListNode> _next;//这里改为智能指针
		std::shared_ptr<ListNode> _prve;
		int _val;
		~ListNode()
		{
			cout << "delete node" << endl;
		}
	};
	void test_shared_ptr2()//改用库里面的智能指针来看看
	{
		//std::shared_ptr  n1 = new ListNode;库里面的shared_ptr加入了explicit,不让类型转换
		//std::shared_ptr  n2 = new ListNode;这里是new出了一个ListNode,然后转换成为shared_ptr
		std::shared_ptr <ListNode> n1(new ListNode);
		std::shared_ptr <ListNode> n2( new ListNode);
		n1->_next = n2;//这里还是有问题,但是去掉这两个赋值就没问题了
		n2->_prve = n1;

		//delete n1;
		//delete n2;

【C++】智能指针_第13张图片
在这里插入图片描述

直接说结论:有了上面两句代码就构成了循环引用,循环引用就导致了内存泄漏

【C++】智能指针_第14张图片

【C++】智能指针_第15张图片

【C++】智能指针_第16张图片

上面这就直接死循环了!我右边对象什么时候销毁取决于左边管理我的指针;而我左边对象什么时候销毁取决于右边管理我的指针
【C++】智能指针_第17张图片

3-5-2-2、循环引用分析

  1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
  2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
  4. 也就是说_next析构了,node2就释放了。
  5. 也就是说_prev析构了,node1就释放了。
  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

【C++】智能指针_第18张图片

循环引用是shared_ptr的一个死穴!出现这种情况无可避免
所以,为了解决这种情况,我们的weak_ptr就登场了!

3-5-2-3、weak_ptr

weak_ptr不支持RAII,它支持对shared_ptr的拷贝,重要的是weak_ptr不会增加引用计数

struct ListNode
	{
		//std::shared_ptr _next;
		//std::shared_ptr _prve;
		// 可以指向资源/访问资源,不参与资源管理,不增加引用计数
		std::weak_ptr<ListNode> _next;
		std::weak_ptr<ListNode> _prve;
		int _val;
		~ListNode()
		{
			cout << "delete node" << endl;
		}
	};
	void test_shared_ptr2()
	{
		std::shared_ptr <ListNode> n1(new ListNode);
		std::shared_ptr <ListNode> n2( new ListNode);
		n1->_next = n2;
		n2->_prve = n1;
		cout << n1.use_count() << endl;
		cout << n1.use_count() << endl;
	}

【C++】智能指针_第19张图片
我们自己写一个迷你版的weak_ptr:

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}
		weak_ptr(const shared_ptr<T>& sp)
			//:_ptr(sp._ptr)
			: _ptr(sp.get())

		{}
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//_ptr = sp._ptr;//private成员
			_ptr = sp.get();
			return *this;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
	};

【C++】智能指针_第20张图片
了解知识:

make_shared本质和shared没什么区别,但是make_shared可以把锁、引用计数开到资源空间的附近位置,减少内存碎片

3-6、定制删除器

如果不是new出来的对象如何通过智能指针管理呢?其实shared_ptr设计了一个删除器来解决这个问题

【C++】智能指针_第21张图片

//定制删除器
template<class T>
struct DeleteArray
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
		cout << "delete [] " << ptr << endl;
	}
};

int main()
{
	//shared_ptr智能指针可以自动调用析构销毁,但是有些场景(自定义类型new【】,fopen打开文件....)
	std::shared_ptr<int> sp1(new int[10], DeleteArray<int>());
	std::shared_ptr<string> sp2(new string[10], DeleteArray<string>());

	std::shared_ptr<string> sp3(new string[10], [](string* ptr){delete[] ptr; });//lambda表达式
	std::shared_ptr<FILE> sp4(fopen("Test.cpp", "r"), [](FILE* ptr){fclose(ptr); });

	return 0;
}

shared_ptr.h

template<class T>
	class default_delete//默认删除器
	{
	public:
		void operator()(T* ptr)
		{
			delete ptr;
		}
	};

	template<class T, class D = default_delete<T>>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)//默认构造
			:_ptr(ptr)
			, _pcount(new int(1))
			, _pmtx(new mutex)
		{}
		
		~shared_ptr()
		{
			Release();
		}
		
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			, _pmtx(sp._pmtx)
		{
			_pmtx->lock();
			++(*_pcount);
			_pmtx->unlock();

		}
		
		void Release()
		{
			bool flag = false;
			_pmtx->lock();
			if (--(*_pcount) == 0)
			{
				delete _pcount;
				//delete _ptr;
				//定制删除器来删除
				_del(_ptr);//不传删除器就用默认的
				flag = true;
			}
			_pmtx->unlock();
			if (flag == true)
				delete _pmtx;
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				Release();

				_pcount = sp._pcount;
				_ptr = sp._ptr;
				_pmtx = sp._pmtx;

				_pmtx->lock();
				++(*_pcount);
				_pmtx->unlock();

			}
			return *this;
		}
		
		int use_count()
		{
			return *_pcount;
		}
		
		T* get()const
		{
			return _ptr;
		}
		
		T& operator*()
		{
			return *_ptr;
		}
		
		T* operator->()
		{
			return _ptr;
		}
		
		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
		
	private:
		T* _ptr;
		int* _pcount;//堆区开辟引用计数
		mutex* _pmtx;//堆区开辟锁空间

		D _del;//定制删除器
	};

	//定制删除器 --- 释放/销毁方式不对就会崩
	template<class T>
	struct DeleteArray
	{
		void operator()(const T* ptr)
		{
			delete[] ptr;
			cout << "delete [] " << ptr << endl;
		}
	};

	struct Fclose
	{
		void operator()(FILE* ptr)
		{
			fclose(ptr);
		}
	};
	void test_shared_ptr3()
	{
		//std::shared_ptr sp1(new int[10], DeleteArray()); 
		//std::shared_ptr sp2(new string[10], DeleteArray());
		//我们自己实现的定制删除器,不能够直接在构造函数传,我们在类的参数去传
		bzh::shared_ptr <ListNode> n1(new ListNode);//默认删除器
		bzh::shared_ptr <ListNode, DeleteArray<ListNode>> n2(new ListNode[10]);//在模板那里传

		//这里我们的用lambda不行,lambda是一个匿名对象,而这里我们要传类型
		// decltype是运行时的类型推到,我们要编译时就要拿到类型
		//bzh::shared_ptr n2(fopen("Test.cpp", "r"));

		bzh::shared_ptr<FILE, Fclose> n3(fopen("Test.cpp", "r"));//不传Fclose就是使用delete,就会崩

		/*std::unique_ptr up3(fopen("Test.cpp", "r"));*/

		std::unique_ptr<FILE, Fclose> up3(fopen("Test.cpp", "r"));
	}
}

4、C++11和boost中智能指针的关系

  1. C++ 98 中产生了第一个智能指针auto_ptr.
  2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
  3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
  4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

5、总结

智能指针对于C++来说还是很重要的,在特殊场景能够帮助我们省略复杂的代码等过程,还是需要大家好好掌握的
我们后面的定制删除器等内容面试考试等地方大概率不考,我们以后做项目或者工作会用即可。但是shared_ptr和unique_ptr,weak_ptr还是经常遇到的,所以要把这几个智能指针重点掌握好!

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