[C++]---智能指针介绍简单模拟实现

目录

 

RAII

模拟实现SmartPtr

模拟实现SmartPtr:

智能指针的原理:

常见的三种智能指针

auto_ptr

模拟测试 C++98版本的库提供的auto_ptr​

auto_pt模拟实现

unique_ptr

使用库里面的unique_ptr

模拟实现unique_ptr

shared_ptr

使用库里面的shared_ptr

模拟实现shared_ptr

在多线程条件下的shared_ptr

shared_ptr的循环引用问题

shared_ptr小结

仿函数定制删除器

守卫锁


RAII

RAII(Resource Acquisition Is Initialization)根据对象的生命周期控制,初始化构造对象时管理资源,销毁对象在对象析构时释放资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。这么做的好处:

  1. 不需要显式地释放资源。
  2. 采用这种方式,对象所需的资源在其生命期内始终保持有效。
// 使用RAII思想设计的SmartPtr类
template
class SmartPtr {
public:
    SmartPtr(T* ptr = nullptr)
    : _ptr(ptr)
    {}
    ~SmartPtr()
    {
       if(_ptr)
       delete _ptr;
    }
private:
    T* _ptr;
};
void MergeSort(int* a, int n)
{
    int* tmp = (int*)malloc(sizeof(int)*n);
    // 讲tmp指针委托给了sp对象,用时老师的话说给tmp指针找了一个可怕的女朋友!天天管着你,直到你go die
   SmartPtr sp(tmp);
   // _MergeSort(a, 0, n - 1, tmp);
   // 这里假设处理了一些其他逻辑
   vector v(1000000000, 10);
   // ...
}
int main()
{
   try {
       int a[5] = { 4, 5, 2, 3, 1 };
       MergeSort(a, 5);
   }
   catch(const exception& e)
   {
       cout<

注意:并没有实现智能指针,只是实现了一个可以管理资源的类。

模拟实现SmartPtr

模拟实现SmartPtr:

智能指针:1.实现RAII思想,2.实现指针的操作(->   和   *)

//智能指针:1.实现RAII思想,2.实现指针的操作
template
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
		{
			delete _ptr;
			cout << "delete" << endl;
			_ptr = nullptr;
		}
	}

	//模拟实现指针功能:  *  和  ->

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

private:
	T* _ptr;
};

int main()
{
	
	//常规的指针
	int* pi = new int(2);
	cout << *pi << endl;
	*pi = 3;
	cout << *pi << endl;
	//普通指针malloc后需要手动释放
	delete pi;
	//智能指针
	cout << "SmartPtr:" << endl;
	SmartPtr sp(new int(10));
	cout << *sp << endl;
	*sp = 20;
	cout << *sp << endl;
	//智能指针编译器会在指针对象使用完成后,进行自动调用析构函数进行释放。
    return 0;
}

[C++]---智能指针介绍简单模拟实现_第1张图片

智能指针的原理:

  • 1. RAII特性
  • 2. 重载operator*和opertaor->,具有像指针一样的行为。

常见的三种智能指针

auto_ptr

模拟测试 C++98版本的库提供的auto_ptr

在使用库里面提供的智能指针,在进行多个智能指针对象管理同一份资源,仅被释放一次。

class A
{
public:
	int _a = 1;
	int _b = 2;
	int _c = 3;
	~A()
	{
		cout << "~A()" << endl;
	}
};


int main()
{

	//多个智能指针对象管理同一份资源,仅被释放一次。
	//一般禁止使用
	auto_ptrsp3(new A());//库里的智能指针会优化,对多个对象管理一份资源只释放一次
	sp3->_a = 20;
	cout << sp3->_a << endl;

	auto_ptrcopy(sp3);
	copy->_a = 100;
	cout << copy->_a << endl;
	auto_ptrcopy2(copy);
	copy2->_b = 1;
	cout << copy2->_b << endl;
    return 0;
}

[C++]---智能指针介绍简单模拟实现_第2张图片 

假如我们使用自己上面写的SmartPtr进行多个对象管理同一份资源时,在使用结束后就会出错,出现二次释放问题

#include
// C++库中的智能指针都定义在memory这个头文件中
#include

using namespace  std;
template
class SmartPtr {
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
		{
			delete _ptr;
			cout << "delete" << endl;
			_ptr = nullptr;
		}
	}

	//模拟实现指针功能:  *  和  ->

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

private:
	T* _ptr;
};

class A
{
public:
	int _a = 1;
	int _b = 2;
	int _c = 3;
	~A()
	{
		cout << "~A()" << endl;
	}
};


int main()
{
	//使用自己定义的智能指针多次释放
	SmartPtrsp3(new A());
	sp3->_a = 20;
	cout << sp3->_a << endl;
	SmartPtr copy (sp3);//假如多个我们自己实现智能指针同时管理一份资源,就会造成资源二次释放。
	copy->_a = 100;
	SmartPtr copy2 (copy);
	copy2->_b = 1;
	

	//system("pause");
	return 0;
}

[C++]---智能指针介绍简单模拟实现_第3张图片 

//测试 系统中auto_ptr智能指针,在对同一块空间被多个对象使用会不会造成程序奔溃问题
#include
// C++库中的智能指针都定义在memory这个头文件中
#include
using namespace  std;

class A
{
public:
	int _a = 1;
	int _b = 2;
	int _c = 3;
	~A()
	{
		cout << "~A()" << endl;
	}
};


int main()
{	
    auto_ptrsp3(new A());//库里的智能指针
	sp3->_a = 20;
	cout << sp3->_a << endl;

	auto_ptrcopy(sp3);//这里发生一次拷贝,顺便将资源的管理权交给新的对象,它不会再拥有管理资源的能力
	
	//sp3已经完成对资源管理权的转移,因此无法访问到_a成员,sp3指针被悬空,因此在这里程序会崩溃,空指针无法解引用。
	//指针悬空
	//sp3->_a = 1;

	copy->_a = 100;
	auto_ptrcopy2(copy);
	//管理权转移,指针悬空,因此不能解引用访问。
	//copy->_b = 1;

	copy2->_b = 1;

	return 0;
}

假如在上面程序中我们去掉//sp3->_a = 1;  和  //copy->_b = 1;注释,运行程序结果如下:

[C++]---智能指针介绍简单模拟实现_第4张图片

崩溃原因:sp3已经完成对资源管理权的转移,因此它便无法访问到_a成员,sp3指针因此会被悬空,所以在这里程序会崩溃,空指针无法解引用。下面的copy原因同理类似。

auto_pt模拟实现

auto_ptr的实现原理:实现管理权转移的思想

拷贝时发生管理权转移,会导致当前被拷贝的智能指针悬空,最终会导致智能指针访问异常,程序崩溃,因此auto_ptr 禁止使用。

//模拟实现auto_ptr智能指针: 解决之前的auto_ptr存在二次释放问题
template
class AutoPtr {
public:
	AutoPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~AutoPtr()
	{
		if (_ptr)
		{
			delete _ptr;
			cout << "delete" << endl;
			_ptr = nullptr;
		}
	}

	// 一旦发生拷贝,就将ap中资源转移到当前对象中,然后另ap与其所管理资源断开联系,
	// 这样就解决了一块空间被多个对象使用而造成程序奔溃问题
	//浅拷贝,指针只是管理资源,不拥有资源
	//管理权转移 拷贝构造
	AutoPtr(AutoPtr& ap)
		:_ptr(ap._ptr)
	{
		ap._ptr = nullptr;
	}

	//赋值运算符重载
	AutoPtr& operator=(AutoPtr& ap)
	{
		// 检测是否为自己给自己赋值
		if (this != ap)
		{
			// 释放当前对象中资源
			if (_ptr)
				delete _ptr;
			//管理权转移
			// 转移ap中资源到当前对象中
			_ptr = ap._ptr;
			ap._ptr = nullptr;	
		}
		return *this;
	}

	//实现指针功能:解引用 ->

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

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

private:
	T* _ptr;
};

class A
{
public:
	int _a = 1;
	int _b = 2;
	int _c = 3;
	~A()
	{
		cout << "~A()" << endl;
	}
};

int main()
{
	AutoPtr sp4(new A());
	sp4->_a = 10;

	AutoPtr copy3(sp4);
	copy3->_a = 100;

	AutoPtr copy4(copy3);
	copy4->_b = 1;
	//只会调用一次析构 一次 delete
	//system("pause");
	return 0;
}

unique_ptr

使用库里面的unique_ptr

#include
// C++库中的智能指针都定义在memory这个头文件中
#include
using namespace  std;

int main()
{
	unique_ptr up(new A());
	up->_a = 10;
	
	unique_ptr up2(new A());
	//使用库里面unique_ptr:它禁止相互间的赋值行为,防赋值
	up2 = up;
	//使用库里面unique_ptr:它禁止相互间的拷贝行为,防拷贝
	unique_ptr copy(up);
    return 0;
}

[C++]---智能指针介绍简单模拟实现_第5张图片

我们可以看到库里面的unique_ptr是防拷贝,防赋值。

模拟实现unique_ptr

只需要在上面实现SmartPtr 基础上禁止掉拷贝构造和赋值运算符重载函数。而在禁止掉拷贝构造和赋值运算符重载函数:有两种方法 {1}使用C++11语法delete  {2}将两个函数定义为私有,只声明不实现

#include
using namespace  std;

//模拟实现Unique_Ptr:只需要禁止掉拷贝构造和赋值运算符重载函数
template
class UniquePtr {
public:
	UniquePtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~UniquePtr()
	{
		if (_ptr)
		{
			delete _ptr;
			cout << "delete" << endl;
			_ptr = nullptr;
		}
	}

	//禁止掉拷贝构造和赋值运算符重载函数:有两种方法 {1}使用C++11语法delete  {2}将两个函数定义为私有,只声明不实现

	//禁止拷贝构造 
	//C++11语法 声明成delete函数
	UniquePtr(UniquePtr& ap) = delete;
	//C++11语法
	//禁止赋值运算符重载 声明成delete函数
	UniquePtr& operator=(UniquePtr& ap) = delete;

	//实现指针功能:解引用 还有  ->

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

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

private:

	//定义成私有,只声明不实现
	//防拷贝构造 
	UniquePtr(UniquePtr& ap);
	//防赋值运算符重载
	UniquePtr& operator=(UniquePtr& ap);

	T* _ptr;
};


class A
{
public:
	int _a = 1;
	int _b = 2;
	int _c = 3;
	~A()
	{
		cout << "~A()" << endl;
	}
};

int main()
{
	
	//使用自己实现的UniquePtr测试拷贝和赋值操作
	UniquePtr up3(new A());
	UniquePtr copy2(up3);
	UniquePtr up4(new A());
	up4 = up3;

	//system("pause");
    return 0;
}

[C++]---智能指针介绍简单模拟实现_第6张图片

unique_ptr特点:防拷贝,防赋值,安全的,可以使用。缺陷:不能进行拷贝赋值。

shared_ptr

使用库里面的shared_ptr

#include
// C++库中的智能指针都定义在memory这个头文件中
#include
using namespace  std;

class A
{
public:
	int _a = 1;
	int _b = 2;
	int _c = 3;
	~A()
	{
		cout << "~A()" << endl;
	}
};
int main()
{
	shared_ptr sp(new A());
	//查看引用计数,看当前资源被几个指针管理
	cout << sp.use_count() << endl;//1
	//可以拷贝
	shared_ptr cope(sp);
	cout << cope.use_count() << endl;//2
	//可以赋值
	shared_ptr sp2(new A());
	cout << sp2.use_count() << endl;//1
	sp2 = sp;
	cout << sp2.use_count() << endl;//3
	//程序结束释放两次delte
	system("pause");
	return 0;
}

[C++]---智能指针介绍简单模拟实现_第7张图片

我们发现系统提供的shared_ptr是完全安全的。

模拟实现shared_ptr

我们在shared_ptr模拟实现:在释放资源时采用引用计数方法,因此在++引用计数或者--引用计数时必须保证原子性操作,因此我们使用了  mutex* _mtx 进而保证原子性操作

#include
#include
using namespace std;
//shared_ptr模拟实现:释放时采用引用计数方法
template
class SharedPtr{
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		, _pcount(new int(1))
		, _mtx(new mutex())
	{}

	SharedPtr(SharedPtr& sp)
		:_ptr(sp._ptr)
		, _pcount(sp._pcount)
		, _mtx(sp._mtx)
	{
		_mtx->lock();
		++(*_pcount);
		_mtx->unlock();
	}
	
	SharedPtr& operator=(SharedPtr& sp)
	{
		//if (this != sp)
		if (_ptr != sp._ptr)
		{
			_mtx->lock();
			--(*_pcount);
			_mtx->unlock();
			if (*_pcount == 0)
			{
				delete _pcount;
				delete _ptr;
			}
			_ptr = sp._ptr;
			_pcount = sp._pcount;

			_mtx->lock();
			++(*_pcount);
			_mtx->unlock();
		}
		return *this;
	}

	~SharedPtr()
	{
		_mtx->lock();
		--(*_pcount);
		_mtx->unlock();
		if (*_pcount == 0)
		{
			if (_ptr)
			{
				delete _ptr;
				delete _pcount;
				cout << "delete _ptr" << endl;
				_ptr = nullptr;
				_pcount = nullptr;
			}
		}
	}

	//实现指针功能:解引用 还有  ->
	T& operator*()
	{
		return *_ptr;
	}

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


	int useCount()
	{
		return *_pcount;
	}

private:
	T* _ptr;

	int *_pcount;
	//不能给成静态的  不能使用一个引用计数表示所有资源被引用的数量,应该每一个资源都拥有自己的引用计数
	//static int _count;

	mutex* _mtx;//保证原子性操作
};

//template
//int SharedPtr::_count = 0;

int main()
{
	SharedPtr sp(new int(1));
	SharedPtr sp2(new int(2));
	cout << sp.useCount() << endl;//1
	cout << sp2.useCount() << endl;//1
	SharedPtr copy(sp);
	cout << sp.useCount() << endl;//2
	cout << copy.useCount() << endl;//2

	sp2 = sp;
	cout << sp.useCount() << endl;//3
	cout << sp2.useCount() << endl;//3
	copy = sp;
	cout << copy.useCount() << endl;//3
	cout << sp.useCount() << endl;//3
	
	system("pause");
	return 0;
}

[C++]---智能指针介绍简单模拟实现_第8张图片

在多线程条件下的shared_ptr

#prama once //防止头文件多次引用
#include
#include
#include
using namespace std;
// C++库中的智能指针都定义在memory这个头文件中
#include

template
class SharedPtr{
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		, _pcount(new int(1))
		, _mtx(new mutex())
	{}

	SharedPtr(SharedPtr& sp)
		:_ptr(sp._ptr)
		, _pcount(sp._pcount)
		, _mtx(sp._mtx)
	{
		/*_mtx->lock();
		++(*_pcount);
		_mtx->unlock();*/
		addRef();
	}

	SharedPtr& operator=(SharedPtr& sp)
	{
		//if (this != sp)
		if (_ptr != sp._ptr)
		{
			/*_mtx->lock();
			--(*_pcount);
			_mtx->unlock();*/
			if (subRef() == 0)
			{
				delete _pcount;
				delete _ptr;
			}
			_ptr = sp._ptr;
			_pcount = sp._pcount;

			/*_mtx->lock();
			++(*_pcount);
			_mtx->unlock();*/
			addRef();
		}
		return *this;
	}

	~SharedPtr()
	{
		
		/*_mtx->lock();
		--(*_pcount);
		_mtx->unlock();*/
		if (subRef() == 0)
		{
			if (_ptr)
			{
				delete _ptr;
				delete _pcount;
				cout << "delete _ptr" << endl;
				_ptr = nullptr;
				_pcount = nullptr;
			}
		}
	}

	//实现指针功能:解引用 还有  ->
	T& operator*()
	{
		return *_ptr;
	}

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


	int useCount()
	{
		return *_pcount;
	}

	int addRef()
	{
		_mtx->lock();
		++(*_pcount);
		_mtx->unlock();
		return *_pcount;
	}

	int subRef()
	{
		_mtx->lock();
		--(*_pcount);
		_mtx->unlock();
		return *_pcount;
	}

private:
	T* _ptr;

	int *_pcount;
	//不能给成静态的  不能使用一个引用计数表示所有资源被引用的数量,应该每一个资源都拥有自己的引用计数
	//static int _count;

	mutex* _mtx;//保证原子性操作
};

#include
mutex mtx;
//使用自己定义的 SharedPtr
void fun(SharedPtr& sp, int n)
{
	for (int i = 1; i <= n; ++i)
	{
		mtx.lock();
		++(*sp);
		mtx.unlock();
		SharedPtr copy(sp);
	}
}
void test1()
{
	int n = 10000;
	SharedPtr sp(new int(0));
	thread t1(fun, sp, n);
	thread t2(fun, sp, n);
	t1.join();
	t2.join();
	cout << *sp << endl;//20000
	cout << sp.useCount() << endl;//1
}
//使用库里面的 shared_ptr
void fun2(shared_ptrsp, int n)
{
	for (int i = 0; i < n; ++i)
	{
		mtx.lock();
		++(*sp);
		mtx.unlock();
		shared_ptr copy(sp);
	}
}


void test2()
{
	int n = 10000;
	shared_ptr sp(new int(0));
	thread t1(fun2, sp, n);
	thread t2(fun2, sp, n);
	t1.join();
	t2.join();
	cout << *sp << endl;//20000
	cout << sp.use_count() << endl;//1
}

int main()
{
	test1();
	//test2();
	system("pause");
	return 0;
}

在这里test1()是我们自己实现的shared_ptr ,而test2()我们使用的库里的shared_ptr 二者进行对比:

[C++]---智能指针介绍简单模拟实现_第9张图片

[C++]---智能指针介绍简单模拟实现_第10张图片

shared_ptr的循环引用问题

//循环引用 造成无法释放结点
template 
class ListNode
{
public:
	shared_ptr> _prev;
	shared_ptr> _next;
	~ListNode()
	{ 
		cout << "~ListNode()" << endl; 
	}
};

void test3()
{
	shared_ptr> sp(new ListNode());
	shared_ptr> sp2(new ListNode());
	cout << sp.use_count() << endl;//1
	cout << sp2.use_count() << endl;//1
	sp->_next = sp2;
	sp2->_prev = sp;
	cout << sp.use_count() << endl;//2
	cout << sp2.use_count() << endl;//2

	//无法释放空间,没调用析构函数
}

int main()
{
	test3();
	system("pause");
	return 0;
}

[C++]---智能指针介绍简单模拟实现_第11张图片 

分析循环引用问题

  • 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成员,所以这就叫循环引用,谁也不会释放。

如何解决?

//解决循环引用方法
template 
class ListNode
{
public:
	//weak_ptr:不会管理资源,也不会修改引用计数
	weak_ptr> _prev;
	weak_ptr> _next;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

void test3()
{
	shared_ptr> sp(new ListNode());
	shared_ptr> sp2(new ListNode());
	cout << sp.use_count() << endl;//1
	cout << sp2.use_count() << endl;//1
	sp->_next = sp2;
	sp2->_prev = sp;
	cout << sp.use_count() << endl;//1
	cout << sp2.use_count() << endl;//1
	
	//weak_ptr: 不可以单独使用,需要借助shared_ptr进行初始化。
	//weak_ptr wp(new int(2));
}

int main()
{
	test3();
	system("pause");
	return 0;
}

[C++]---智能指针介绍简单模拟实现_第12张图片

原理:sp->_next = sp2;和sp2->_prev = sp1;时weak_ptr的_next和_prev不会增加sp1和sp2的引用计数。

注意:weak_ptr: 不可以单独使用,需要借助shared_ptr进行初始化

shared_ptr小结

通过引用计数完成拷贝,引用计数类型为指针类型,拷贝,属于shared_ptr的成员变量,不同的资源会有一个唯一的记录资源被引用的个数的空间,因此它是是安全的,可以使用的。

智能指针如果指向同一个资源,所访问的引用计数即为同一个空间的内容。

在拷贝时进行引用计数的增加,析构时进行引用计数的减减。释放资源:引用计数为0时释放资源。

线程安全:引用计数要保证线程安全,给每一份资源设置一把锁,修改对应的资源的引用计数时,先加锁。保证计数的修改为一个串行的操作,保证线程安全。

仿函数定制删除器

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

//仿函数的删除器,定制删除器
class B
{
public:
	~B()
	{
		cout << "~B()" << endl;
	}
};

template 
class DeleteArray
{
public:
	//仿函数 : 重载括号运算符函数
	void operator() (T* ptr)
	{
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};

template 
class FreeDelete
{
public:
	//仿函数 : 重载括号运算符函数
	void operator() (T* ptr)
	{
		cout << "free:" << ptr << endl;
		free(ptr);
	}
};


void test4()
{
	shared_ptr sp(new int[10], DeleteArray());
	shared_ptr sp2((int*)malloc(100), FreeDelete());

	shared_ptr sp3(new B[10], DeleteArray());
	shared_ptr sp4((B*)malloc(100),FreeDelete());
}

int main()
{
	test4();
	system("pause");
	return 0;
}

[C++]---智能指针介绍简单模拟实现_第13张图片

守卫锁

守卫锁:其实也是利用RAII思想,利用对象声明周期管理锁的生命周期:构造加锁,析构解锁。

//守卫锁 也是利用RAII思想,利用对象声明周期管理锁的生命周期:构造加锁,析构解锁。
template
class LockGuard
{
public:
	LockGuard(Mtx& mtx)
		:_mtx(mtx)
	{
		_mtx.lock();
	}
	~LockGuard()
	{
		cout << "~LockGuard()" << endl;
		_mtx.unlock();
	}
	//防拷贝,   如果可以拷贝的话,导致多个对象同时解锁,而一个锁不能被解锁多次,因此需要防拷贝
	LockGuard(const LockGuard& lg) = delete;

private:
	// 注意这里必须使用引用,不支持拷贝,否则锁的就不是一个互斥量对象
	Mtx& _mtx;
};

mutex mtx;
void fun()
{
	int i;
	cin >> i;
	//mtx.lock();
	LockGuard lg(mtx);
	if (i == 9)
	{
		return;
		cout << i << endl;
	}
	//mtx.unlock();
	
}

int main()
{
	thread t1(fun);
	thread t2(fun);
	t1.join();
	t2.join();
	system("pause");
	return 0;
}

[C++]---智能指针介绍简单模拟实现_第14张图片

你可能感兴趣的:(C++)