C++——C++11智能指针

目录

一,关于智能指针

1.1 场景一

1.2 场景二

1.3 引出智能指针

1.4 RAII

1.5 智能指针拷贝

 二,智能指针类型

2.1 auto_ptr

2.2 unique_ptr

2.3 shared_ptr

2.4 shared_ptr的线程安全问题

2.5 循环引用和weak_ptr

​编辑 三,定制删除器(了解)

3.1 场景

3.2 使用包装器解决

3.3 使用仿函数解决

一,关于智能指针

1.1 场景一

先看下面代码:

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

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

	cout << div() << endl;

	delete p1;
	delete p2;
	cout << "释放资源" << endl;
}

void main()
{
	try
	{
		Func1();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
}

我们在指针Func1函数,申请空间并且调用除法函数,先回答代码注释的三个问题:

①如果在new p1的时候抛异常了,没有影响

②如果在new p2的时候抛异常了,会直接跳到main的catch去,导致后面的delete p2无法执行,造成内存泄漏

③如果div里面抛异常了,和上面一样,p1和p2都无法释放,内存泄漏

1.2 场景二

对于上面的问题,我们可以这样解决:

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

void Func1()
{
	int* p1 = new int;
	int* p2 = nullptr;
	try
	{
		p2 = new int;
	}
	catch (...)
	{
		delete p1;
		throw;
	}
	cout << div() << endl;
    
	delete p1;
	delete p2;
    cout << "释放p1和p2" << endl;
}
void main()
{
	try
	{
		Func1();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
}

如上代码,如果new p2时抛异常,我们可以在Func1中先捕获异常,delete p1,然后再抛异常给main,但是,如果是div抛异常,不论怎样,div都会从throw的位置跳到main的捕获,无法释放p1和p2,而且p1和p2在Func1的函数栈帧里,所以也无法在main和div里调用delete释放,造成内存泄漏。

1.3 引出智能指针

上面的问题传统处理方式是很棘手的,所以C++11中推出了智能指针的概念,具体看下列代码和注释

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr = nullptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
		{
			cout << "Delete:" << _ptr << endl;
			delete _ptr;
		}
	}
	//智能指针是指针,所以可以像指针一样解引用和->
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

void Func2()
{
	SmartPtr<int> sp1(new int);//把申请的资源交给适sp1,通过sp1的释放来释放p1
	SmartPtr<int> sp2(new int);
	//不论是Func正常结束还是抛异常,sp1和sp2都会调用析构函数释放资源
	cout << div() << endl;
}

class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
	int _a1 = 0;
	int _a2 = 0;
};

void main1()
{
	SmartPtr<A> ap1(new A);//A自己调用析构函数
	ap1->_a1++;
	ap1->_a2++;

    //智能指针的拷贝问题
	//SmartPtr<A> ap2(ap1);
	//拷贝时直接报错,浅拷贝,同一块地方释放两次,而且不能深拷贝,因为违背了功能需求
}

 如上面代码,我们把new出来的对象通过智能指针类的构造函数交给类管理,这样,我们new的资源就会随着智能指针类的构造函数初始化而初始化,随类的析构而释放,可以解决场景一二的问题。

这种通过类来管理资源的方式叫做RAII

void main()
{
	SmartPtr<pair<string, int>> sp1(new pair<string, int>("sort", 1));
	sp1->second++;
	cout << sp1->first << ":" << sp1->second << endl;
}

1.4 RAII

RAII是一种利用声明周期来控制程序资源的简单技术。

在对象构造时获取资源,接着控制对资源的访问使之在生命周期内始终保持有效,最后在对象析构时释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象,这种做法有两大好处:

①不需要显示地释放资源

②采用这种方式,对象所需地资源在其生命周期内始终保持有效

1.5 智能指针拷贝

就上面地SmartPtr来说,如果我们SmartPtr<A> ap2(ap1)时,看似没啥问题,但是最后函数结束地时候必定报错.本来是ap1管资源,现在拷贝后ap1和ap2一起管了,最后析构时会对这块资源析构两次,典型的浅拷贝问题。而且不能深拷贝,因为违背了功能需求,我们只是需要管理这块资源,并不需要额外空间

所以,为了解决这种问题,C++11给出了多种智能指针类型,对拷贝问题给出了多种解决方案

 问题:链表/红黑树等迭代器跟智能指针结构类型,但是迭代器用的是浅拷贝,为啥没问题?

因为迭代器不管资源释放,它只管访问资源,修改资源,因为释放资源的事情是交给析构函数的。而指针指针需要资源释放,所以不能单纯地浅拷贝

 二,智能指针类型

class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
	int _a1 = 0;
	int _a2 = 0;
};

2.1 auto_ptr

C++98的库中就提供了auto_ptr的智能指针,但是它的拷贝是一种极其“不负责任”的拷贝,它只实现资源管理权限的转移,会导致对象被拷贝走后悬空,再次访问就会报错。(多年来被挂在耻辱柱上,很多公司明确要求不能使用它)

namespace bit
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr = nullptr)
			: _ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>& ap)//模拟实现std中auto_ptr的拷贝功能
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}

		// ap1 = ap2;
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			if (this != &ap)
			{
				if (_ptr)
				{
					cout << "Delete:" << _ptr << endl;
					delete _ptr;
				}

				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}

			return *this;
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "Delete:" << _ptr << endl;
				delete _ptr;
			}
		}

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

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

	private:
		T* _ptr;
	};
}

void test_auto_ptr()
	{
		bit::auto_ptr<A> ap1(new A);
		ap1->_a1++;
		ap1->_a2++;

		//std::auto_ptr<A> ap2(ap1);程序直接挂了
		bit::auto_ptr<A> ap2(ap1);//拷贝,管理权转移,直接把ap1变成nullptr了
		//*ap1; 管理权转移后大致ap1悬空,这时候对ap1进行任何解引用操作会直接报错
		ap2->_a1++;
		ap2->_a2++;

		// 2 2
		cout << ap2->_a1 << endl;
		cout << ap2->_a2 << endl;

		bit::auto_ptr<A> ap3(new A);
		ap2 = ap3;//管理权转移,有一次析构

		ap2->_a1++;
		ap2->_a2++;

		// 1 1
		cout << ap2->_a1 << endl;
		cout << ap2->_a2 << endl;
	}

2.2 unique_ptr

unique_ptr非常简单粗暴,它直接把拷贝和赋值给禁掉了,直接从根源上解决问题

namespace bit
{
	template<class T>
	class unique_ptr
	{
	private:
		// 防拷贝 C++98的解决方法
		// 只声明成私有,并且不实现,私有是因为可能会在类外面实现
		//unique_ptr(unique_ptr<T>& ap);
		//unique_ptr<T>& operator=(unique_ptr<T>& ap);
	public:
		unique_ptr(T* ptr = nullptr)
			: _ptr(ptr)
		{}

		// 防拷贝 C++11的解决方法
		// 直接把拷贝和赋值禁掉
		unique_ptr(unique_ptr<T>& ap) = delete;
		unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "Delete:" << _ptr << endl;
				delete _ptr;
			}
		}

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

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

	private:
		T* _ptr;
	};
}

//不需要拷贝的场景就用这个,但是我们还是有需要拷贝的场景的
void test_unique_ptr()
{
	bit::unique_ptr<A> up1(new A);
	//std::unique_ptr<A> up2(up2);//直接报错,不让你拷贝
	up1->_a1++;
	up1->_a2++;

	bit::unique_ptr<A> up3(new A);
	//up1 = up2;
}

2.3 shared_ptr

unique_ptr已经能解决大部分问题了,但是有些场景下我们还是需要拷贝的,所以我们推出了shared_ptr。它采用对资源计数的方法控制资源的释放

namespace bit
{   
	//一个资源,配一个计数,资源由多个指针对象共同管理
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			: _ptr(ptr)
			, _pCount(new int(1))
		{}

		void Release()
		{
			if (--(*_pCount) == 0)
			{
				if (ptr)
                {
	                cout << "delete:" << ptr << endl;
	                delete ptr;
                }
				delete _pCount;
			}
		}

		void AddCount()
		{
			++(*_pCount);
		}

		~shared_ptr()
		{
			Release();
		}

		//sp1(sp2)
		shared_ptr(shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pCount(sp._pCount)
		{
			AddCount();
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if(_ptr = &sp) 这种只能防sp1 = sp1, 如果是是sp1 = sp5后再次sp5 = sp1,就用下面这个
			if (_ptr == sp._ptr)
				return *this;

			//赋值时如果计数为一,则代表sp1是目前管理这块空间的唯一者,现在这个唯一者要被赋值替换走,那么释放这块空间
			Release();

			//sp1 = sp2
			_ptr = sp._ptr;           //将指向sp2资源的指针覆盖sp1的指针
			_pCount = sp._pCount;     //sp2的计数也改一下

			AddCount();             //计数++,上面的Release已经释放了sp1的空间
			return *this;
		}

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

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

		int use_count()
		{
			return *_pCount;
		}

		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pCount;//引用计数
		//这个计数要求是公共的,但是不能定义成static,因为如果是静态的,就属于这个类的所有对象
		//假设sp1,sp2,sp3指向一个空间,计数为3,但是如果sp4重新开了一块空间,那么计数就变成了1,这是个大问题
		//多个对象可能多个资源,所以应该是每个资源配对一个引用计数,不是对象
	};
}

// shared_ptr 如果就是需要拷贝的场景
void test_shared_ptr()
{
	bit::shared_ptr<A> sp1(new A);
	bit::shared_ptr<A> sp2(sp1);
	bit::shared_ptr<A> sp3(sp2);

	sp1->_a1++;
	sp1->_a2++;
	cout << sp2->_a1 << ":" << sp2->_a2 << endl;
	sp2->_a1++;
	sp2->_a2++;
	cout << sp1->_a1 << ":" << sp1->_a2 << endl;

	bit::shared_ptr<A> sp5(new A);
	bit::shared_ptr<A> sp6(sp5);

	sp1 = sp5;
	sp2 = sp5;
	sp3 = sp5;

	// 自己给自己赋值
	bit::shared_ptr<int> sp4(new int);
	sp4 = sp4;
	sp1 = sp5;
}

2.4 shared_ptr的线程安全问题

一智能指针对象中引用计数是多个线程对象共享的,所以多个线程可以同时对计数++或--,多以这个操作不是原子的,不是线程安全的。所以在对计数++或--的时候需要加锁和释放锁

所以我们来改造shared_ptr:

namespace bit
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pCount(new int(1))
			, _pmtx(new mutex)
		{}

		void Release()
		{
			_pmtx->lock();
			bool deleteFlag = false;
			if (--(*_pCount) == 0)

			{
				cout << "Delete:" << _ptr << endl;
				delete _ptr;

				delete _pCount;
				deleteFlag = true; //用一个bool来控制锁的释放
			}
			_pmtx->unlock();
			if (deleteFlag)
			{
				delete _pmtx;
			}
		}

		void AddCount()
		{
			_pmtx->lock();
			++(*_pCount);
			_pmtx->unlock();
		}

		~shared_ptr()
		{
			Release();
		}

		//sp1(sp2)
		shared_ptr(shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pCount(sp._pCount)
			, _pmtx(sp._pmtx)
		{
			AddCount();
		}

		//sp1 = sp5
		//sp1 = sp1
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if(_ptr = &sp) 这种只能防sp1 = sp1, 如果是是sp1 = sp5后再次sp5 = sp1,就用下面这个
			if (_ptr == sp._ptr)
				return *this;

			//赋值时如果计数为一,则代表sp1是目前管理这块空间的唯一者,现在这个唯一者要被赋值替换走,那么释放这块空间
			Release();

			//sp1 = sp2
			_ptr = sp._ptr;           //将指向sp2资源的指针覆盖sp1的指针
			_pCount = sp._pCount;     //sp2的计数也改一下
			_pmtx = sp._pmtx;
			AddCount();             //计数++,上面的Release已经释放了sp1的空间
			return *this;
		}

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

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

		int use_count()
		{
			return *_pCount;
		}

		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pCount;//引用计数
		//这个计数要求是公共的,但是不能定义成static,因为如果是静态的,就属于这个类的所有对象
		//假设sp1,sp2,sp3指向一个空间,计数为3,但是如果sp4重新开了一块空间,那么计数就变成了1,这是个大问题
		//多个对象可能多个资源,所以应该是每个资源配对一个引用计数,不是对象
		mutex* _pmtx;
	};
}

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

	~Date()
	{}
};
//shared_ptr本身是线程安全的,因为计数是加锁保护的
void SharePtrFunc(bit::shared_ptr<Date>& sp, size_t n, mutex& mtx)
{
	//cout << &sp << endl; 虽然这里给的是引用,但是下面的p没有直接传给sp,sp接收的是p的拷贝,所以要加ref
	cout << sp.get() << endl;
	for (size_t i = 0; i < n; ++i)
	{
		// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
		bit::shared_ptr<Date> copy(sp);
		// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但是最终看到的结果,不一定是加了2n
		mtx.lock();
		sp->_year++;
		sp->_month++;
		sp->_day++;
		mtx.unlock();
	}
}

void test_shared_ThreadSafe()
{
	bit::shared_ptr<Date> p(new Date);
	cout << p.get() << endl;
	const size_t n = 50000;
	mutex mtx;
	thread t1(SharePtrFunc, std::ref(p), n, std::ref(mtx)); //规定想在线程的参数里传引用需要这样传
	thread t2(SharePtrFunc, std::ref(p), n, std::ref(mtx)); //

	t1.join();
	t2.join();

	cout << p.use_count() << endl;

	cout << p->_year << endl;
	cout << p->_month << endl;
	cout << p->_day << endl;
}

2.5 循环引用和weak_ptr

struct Node
{
	int _val;
	//n1->_next = n2; 无法赋值,因为n1是智能指针,_next是原生指针,所以Node里面也要搞成智能指针
	bit::shared_ptr<Node> _next;
	bit::shared_ptr<Node> _prev;

	~Node()
	{
		cout << "~Node()" << endl;
	}
};
void test_shared_cycle()
{
	bit::shared_ptr<Node> n1(new Node);//无法n1 = new Node,不支持隐式类型转换
	bit::shared_ptr<Node> n2(new Node);

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;
	
	//不增加引用计数
	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
}

 观察上面代码,我们用智能指针作为链表的的前后指针类型,然后我们定义两个节点,让它们的前后都指向自己,这时候,我们发现,当函数结束后,不打印Node的析构函数,这就代表着未执行节点的析构函数,节点资源未释放。为什么呢?看下图

这是我们双链表的基本结构,两个节点,里面都有两个指针,类型为shared_ptr,当函数结束后要释放资源,首先释放n1,_val被释放,然后shared_ptr调用自己的析构函数,本来没啥问题,但是释放_next时,除了释放_next自己的资源时,还会去释放它指向的资源,它指向的是n2,所以再释放n2,然后释放n2时,_prev和_next都调用自己的析构,然后再去释放它们指向的资源,然后它们指向n1。

所以这样就形成了一种死循环,n1啥时候释放取决于n2,n2啥时候释放取决于n1,冤家路窄,狭路相逢,谁也不放过谁,就无法释放资源了,造成内存泄漏。这种情况我们叫做循环引用

所以我们用weak_ptr来解决循环引用,它是一个辅助型智能指针,是专门设计出来的解决shared_ptr的循环引用问题的。它不是常规的智能指针,所以不支持RAII,weak_ptr可以指向资源,但是不参与管理,也就是不对引用计数进行++或--

//shared_ptr的循环引用问题
//辅助型智能指针,配合解决shared_ptr循环引用问题
//它不是常规的智能指针,不支持RAII
//专门设计出来,辅助解决shared_ptr的循环引用问题
//weak_ptr可以指向资源,但是不参与管理(不增加引用计数)
namespace bit
{
	template<class T>
	class weak_ptr //模拟实现极简版本,C++标准库的实现非常复杂
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		weak_ptr(const weak_ptr<T>& wp)
			:_ptr(wp._ptr)
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}

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

		T* operator->()
		{
			return _ptr;
		}
	public:
		T* _ptr;
	};
}
struct Node
{
	int _val;
	//n1->_next = n2; 无法赋值,因为n1是智能指针,_next是原生指针,所以Node里面也要搞成智能指针
	/*bit::shared_ptr<Node> _next;
	bit::shared_ptr<Node> _prev;*/

	bit::weak_ptr<Node> _next;
	bit::weak_ptr<Node> _prev;
	~Node()
	{
		cout << "~Node()" << endl;
	}
};
void test_shared_cycle()
{
	bit::shared_ptr<Node> n1(new Node);//无法n1 = new Node,不支持隐式类型转换
	bit::shared_ptr<Node> n2(new Node);

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
	
	n1->_next = n2;
	n2->_prev = n1;
	//节点被delete时,由于_next和_prev都是智能指针会调用自己的析构函数,n1的_next和n2的_prev作为成员会析构
	//n1里面有n2,n2里面有n1,简单来说n1啥时候释放取决于n2,n2啥时候释放取决于n1,谁也不放过谁,冤家路窄,狭路相逢,就无法释放,造成内存泄露
	//所以用weak_ptr解决

	//不增加引用计数
	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
}

 三,定制删除器(了解)

3.1 场景

template<class T>
struct DeleteArray
{
	void operator()(T* ptr)
	{
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};

template<class T>
struct Free
{
	void operator()(T* ptr)
	{
		cout << "free" << ptr << endl;
		free(ptr);
	}
};
//定制删除器 -- 其实就是仿函数,可调用对象
void test_shared_deletor()
{
	//指针偏移
	//std::shared_ptr<Node> n1(new Node[5]); //Date如果我们写了析构会崩,因为new[]时会多开四个字节表示需要调几次析构函数,不写析构函数时不会多开四个字节,那么delete的位置就是对的,但是如果我们写了,那么释放的位置就不对了
	std::shared_ptr<int> n2(new int[5]);     //没崩
	std::shared_ptr<Node> n3(new Node);      //没崩
	std::shared_ptr<int> n4((int*)malloc(sizeof(12)));

	//仿函数
	std::shared_ptr<Node> n5(new Node[5], DeleteArray<Node>());
	std::shared_ptr<int> n7(new int[5], DeleteArray<int>());
	std::shared_ptr<Node> n6(new Node);
	std::shared_ptr<int> n8((int*)malloc(sizeof(12)), Free<int>());


	//这里用lambda就非常爽
	std::shared_ptr<Node> n9(new Node[5], [](Node* ptr){delete[] ptr; });
	std::shared_ptr<int> n10(new int[5], [](int* ptr) {delete[] ptr; });
	std::shared_ptr<Node> n11(new Node);
	std::shared_ptr<int> n12((int*)malloc(sizeof(12)), [](int* ptr){free(ptr); });

	std::shared_ptr<FILE> n13(fopen("test.txt", "w"), [](FILE* ptr){fclose(ptr); });
	std::unique_ptr<Node, DeleteArray<Node>> up(new Node[5]);
}

如同上面的注释部分,如果我们使用[]一下子申请好几个Node,如果我们的Node写了析构函数,到时候开空间的时候会多开一个int用来记录申请的Node个数方便最后调用对应数量的析构函数, 但是最后delete会直接报错

所以针对这种情况,shared_ptr设计了定制删除器来解决这个问题

3.2 使用包装器解决

namespace bit
{
	//一个资源,配一个计数,资源由多个指针对象共同管理
	template<class T>
	class shared_ptr1
	{
	public:
		shared_ptr1(T* ptr = nullptr)
			:_ptr(ptr)
			, _pCount(new int(1))
			, _pmtx(new mutex)
		{}

		template<class D>
		shared_ptr1(T* ptr, D del)
			: _ptr(ptr)
			, _pCount(new int(1))
			, _pmtx(new mutex)
			, _del(del)
		{}

		void Release()
		{
			_pmtx->lock();
			bool deleteFlag = false;
			if (--(*_pCount) == 0)
			{
				if (_ptr)
				{
					//cout << "delete:" << _ptr << endl;
					//delete _ptr;
					_del(_ptr);
				}
			}
			_pmtx->unlock();
			if (deleteFlag)
			{
				delete _pmtx;
			}
		}

		void AddCount()
		{
			_pmtx->lock();
			++(*_pCount);
			_pmtx->unlock();
		}

		~shared_ptr2()
		{
			Release();
		}

		//sp1(sp2)
		shared_ptr2(shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pCount(sp._pCount)
			, _pmtx(sp._pmtx)
		{
			AddCount();
		}

		shared_ptr2<T>& operator=(const shared_ptr2<T>& sp)
		{
			if (_ptr == sp._ptr)
				return *this;

			Release();
			_ptr = sp._ptr;           
			_pCount = sp._pCount;    
			_pmtx = sp._pmtx;
			AddCount();            
			return *this;
		}

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

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

		int use_count()
		{
			return *_pCount;
		}

		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pCount;
		mutex* _pmtx;
		//包装器
		function<void(T*)> _del = [](T* ptr)
			{
				cout << "lamdba delete:" << ptr << endl;
				delete ptr;
			};
	};
}

void test_shared_ptr1()
{
	bit::shared_ptr1<Date> sp0(new Date);

	bit::shared_ptr1<Date> spa1(new Date[10], DeleteArray<Date>());
	bit::shared_ptr1<Date> spa2(new Date[10], [](Date* ptr) {
		cout << "lambda delete[]:" << ptr << endl;
		delete[] ptr;
		});

	bit::shared_ptr1<FILE> spF3(fopen("Test.cpp", "r"), [](FILE* ptr) {
		cout << "lambda fclose:" << ptr << endl;
		fclose(ptr);
		});
}

3.3 使用仿函数解决

	template<class T, class D = Delete<T>>
	class shared_ptr2
	{
	public:
		shared_ptr2(T* ptr = nullptr)
			:_ptr(ptr)
			, _pCount(new int(1))
			, _pmtx(new mutex)
		{}

		void Release()
		{
			_pmtx->lock();
			bool deleteFlag = false;
			if (--(*_pCount) == 0)

			{
				//cout << "Delete:" << _ptr << endl;
				// delete _ptr;

				//D del;
				//del(_ptr)
				D()(_ptr);

				delete _pCount;
				deleteFlag = true;
			}
			_pmtx->unlock();
			if (deleteFlag)
			{
				delete _pmtx;
			}
		}

		void AddCount()
		{
			_pmtx->lock();
			++(*_pCount);
			_pmtx->unlock();
		}

		~shared_ptr()
		{
			Release();
		}

		//sp1(sp2)
		shared_ptr(shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pCount(sp._pCount)
			, _pmtx(sp._pmtx)
		{
			AddCount();
		}

		//sp1 = sp5
		//sp1 = sp1
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if(_ptr = &sp) 这种只能防sp1 = sp1, 如果是是sp1 = sp5后再次sp5 = sp1,就用下面这个
			if (_ptr == sp._ptr)
				return *this;

			//赋值时如果计数为一,则代表sp1是目前管理这块空间的唯一者,现在这个唯一者要被赋值替换走,那么释放这块空间
			Release();

			//sp1 = sp2
			_ptr = sp._ptr;           //将指向sp2资源的指针覆盖sp1的指针
			_pCount = sp._pCount;     //sp2的计数也改一下
			_pmtx = sp._pmtx;
			AddCount();             //计数++,上面的Release已经释放了sp1的空间
			return *this;
		}

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

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

		int use_count()
		{
			return *_pCount;
		}

		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pCount;//引用计数
		//这个计数要求是公共的,但是不能定义成static,因为如果是静态的,就属于这个类的所有对象
		//假设sp1,sp2,sp3指向一个空间,计数为3,但是如果sp4重新开了一块空间,那么计数就变成了1,这是个大问题
		//多个对象可能多个资源,所以应该是每个资源配对一个引用计数,不是对象
		mutex* _pmtx;
	};
}

void test_shared_ptr2()
{
	bit::shared_ptr2<Date> sp0(new Date);

	bit::shared_ptr2<Date> spa1(new Date[10], DeleteArray<Date>());
	bit::shared_ptr2<Date> spa2(new Date[10], [](Date* ptr) {
		cout << "lambda delete[]:" << ptr << endl;
		delete[] ptr;
		});

	bit::shared_ptr2<FILE> spF3(fopen("Test.cpp", "r"), [](FILE* ptr) {
		cout << "lambda fclose:" << ptr << endl;
		fclose(ptr);
		});
}

最后这个shared_ptr建议多看十多次,最好能手撕,因为面试时,经常会让你手撕

  • 11
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值