C++——智能指针

这篇具有很好参考价值的文章主要介绍了C++——智能指针。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

智能指针

C++——智能指针,c++,c++,算法

内存泄漏

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

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

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

  • 堆内存泄漏(Heap leak)

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

  • 系统资源泄漏

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

内存泄漏很常见,解决方案通常分为两种:

  1. 事前预防型。如智能指针等。
  2. 事后查错型。如泄 漏检测工具。

智能指针解决内存泄漏问题

场景:main函数里调用func函数,在func函数里申请了一个int大小的空间,然后调用div函数,在div函数进行除法操作,若出现除零,则直接抛异常,直接跳转到main函数捕获异常,从而没有释放掉在func函数内申请的资源,即内存泄漏。

#include<iostream>
#include<vector>
using namespace std;

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{
	int* ptr = new int;//new了一个int大小的资源
	cout << div() << endl;
	delete ptr;//因为抛异常导致这个int资源没有被正常释放
    cout << "delete ptr" << endl;
}
int main()
{
	try
	{
		func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

解决方法一:在func函数内捕获一次异常,进行对申请资源的释放后,再将异常抛出,让外层栈帧去捕获解决异常。

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{	int* ptr = new int;//new了一个int大小的资源
try {
	cout << div() << endl;
}
catch (...)
{

	delete ptr;//因为抛异常导致这个int资源没有被正常释放
	cout << "delete ptr" << endl;
	throw;
}

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

C++——智能指针,c++,c++,算法

解决方案二:让智能指针对资源进行管理,调用智能指针的构造函数申请资源,调用智能指针的析构函数释放资源。

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;

};

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{	
	smartptr<int> sma(new int);
	cout << div() << endl;	
}
int main()
{
	try
	{
		func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

C++——智能指针,c++,c++,算法

  • 在构造sma对象时,让smartptr的构造函数去申请资源。
  • 当退出func函数这层栈帧时,sma对象的生命周期结束,自动调用smartptr的析构函数去释放资源。
  • 将申请到的资源交给一个smartptr对象进行管理,这样无论是否出现除零都能正常释放资源。
  • 此外为了让smartptr对象能够像原生指针一样使用,还需要对*->运算符进行重载。

智能指针的使用及原理

RAII

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

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

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

需要注意的是:

  1. 在对象构造时获取资源,在对象析构时释放资源,即要具备RAII特性。
  2. *->运算符进行重载,使得对象具有像指针一样的行为。
  3. 智能指针对象的拷贝问题。
智能指针对象的拷贝问题
int main()
{
	smartptr<int> sm1(new int);
	smartptr<int> sm2(sm1);//拷贝构造

	smartptr<int> sm3(new int);//赋值重载
	sm3 = sm1;
    return 0;
}

C++——智能指针,c++,c++,算法

  • 编译器默认生成的拷贝构造是对内置类型完成值拷贝(浅拷贝),因此sm2对象对sm1对象的拷贝构造,是对sm1对象的资源的地址拷贝过来,即sm1和sm2对同一份资源进行管理,当退出栈帧对象的生命周期结束时,sm1和sm2一起会对一份资源析构两次,造成越界问题。
  • 编译器默认生产的拷贝赋值函数是对内置类型完成值拷贝(浅拷贝)。因此将sm1对象赋值給sm3对象,是将sm1管理资源的地址赋值給sm3的_ptr,即sm1和sm3对同一份资源进行管理,当退出栈帧对象的生命周期结束时,sm1和sm3一起会对同一份资源析构两次,造成越界问题。

智能指针要模拟出原生指针的行为,而我们将一个指针赋值給另一个指针,其目的就是让两个指针对同一份资源进行管理,但单纯的浅拷贝会导致空间多次释放,因此根据实现的场景不同,衍生出不同的智能指针。

C++中的智能指针

auto_ptr

auto_ptr的实现目的:对资源的管理权进行转移

auto_ptr是C++98中引入的智能指针,auto_ptr通过管理权转移的方式解决智能指针的拷贝问题,保证一个资源在任何时刻都只有一个对象在对其进行管理,这时同一个资源就不会被多次释放了。

auto_ptr的模拟实现

template<class T>
class Auto_ptr
{
public:
	Auto_ptr(T* ptr=nullptr):_ptr(ptr){}
	~Auto_ptr() { cout << "delete _ptr" << endl; delete _ptr; }

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

	Auto_ptr(Auto_ptr<T>& tp)//拷贝构造
		:_ptr(tp._ptr)
	{
			tp._ptr = nullptr;//悬空
	}

	Auto_ptr& operator=(Auto_ptr<T>& tp)
	{
		if (this!=&tp)//判断是否是自己赋值給自己
		{
			if (_ptr)
			{
				delete _ptr;
				_ptr = nullptr;
			}
			_ptr = tp._ptr;//转移资源管理权
			tp._ptr = nullptr;//悬空
		}
		return *this;
	}
private:
	T* _ptr;
};

简易auto_ptr实现思路:

  1. 在构造函数获取资源,在析构函数释放资源,利用对象的生命周期管理资源。
  2. *->运算符进行重载,使得对象具有像指针一样的行为。
  3. 在拷贝构造函数中,将传入对象的资源来构造当前对象,然后将传入对象管理资源的指针置空。
  4. 在拷贝赋值函数中,,先将当前对象的资源释放,然后将传入对象的资源来构造当前对象,最后将传入对象管理资源的指针置空。

测试代码

int main()
{
	Auto_ptr<int> apt1(new int(1));
	*apt1 = 10;
	Auto_ptr<int> apt2(apt1);

	Auto_ptr<int> apt3(new int(3));
	apt3 = apt2;
	return 0;
}

C++——智能指针,c++,c++,算法

C++——智能指针,c++,c++,算法

unique_ptr

unique_ptr的实现目的:防止拷贝

通过防止在智能指针之间互相拷贝,暴力的解决了多个智能指针对同一块资源进行释放的问题。

template<class T>
class Unique_ptr
{
public:
	Unique_ptr(T* ptr):_ptr(ptr){}
	~Unique_ptr()
	{
		if (_ptr)
		{
			cout << "delete _ptr" << endl;
			delete _ptr;
		}
		
	}

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

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

	Unique_ptr<T>& operator=(const Unique_ptr<T>& upt) = delete;//禁用拷贝构造
	Unique_ptr(const Unique_ptr<T>& upt) = delete;//禁用拷贝赋值
private:
	T* _ptr;
};

int main()
{
	Unique_ptr<int> upt1(new int(1));
	cout << *(upt1) << endl;

	Unique_ptr<int> upt2(upt1);
	Unique_ptr<int> upt3(new int(3));
	upt3 = upt1;
	return 0;
}

简易unique_ptr实现思路:

  1. 在构造函数获取资源,在析构函数释放资源,利用对象的生命周期管理资源。
  2. *->运算符进行重载,使得对象具有像指针一样的行为。
  3. 用C++98的方式将拷贝构造函数和拷贝赋值函数声明为私有,或者用C++11的方式在这两个函数后面加上=delete,防止外部调用。

C++——智能指针,c++,c++,算法

shared_ptr

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

namespace s
{
	template<class T>
	class Share_ptr
	{
	public:
		Share_ptr(T* ptr = nullptr) :_ptr(ptr), _pcount(new int(1)) {}

		Share_ptr<T>& operator=(const Share_ptr<T>& spt)//赋值重载
		{
			if (_ptr != spt._ptr)
			{
				Release();
				_ptr = spt._ptr;
				_pcount = spt._pcount;
				Addpcount();
			}
			return *this;
		}

		int use_count()
		{
			return *_pcount;
		}
		void Addpcount()
		{
			(*_pcount)++;
		}
		Share_ptr(const Share_ptr<T>& spt)//拷贝构造
			:_ptr(spt._ptr)
			, _pcount(spt._pcount)
		{
			Addpcount();
		}
		void Release()
		{
			if (--(*_pcount) == 0 && _ptr)
			{
				//计数为0,释放资源
				cout << "delete _ptr" << endl;
				delete _ptr;
				delete _pcount;
			}
		}
		~Share_ptr()
		{
			Release();
		}

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

		T* get()
		{
			return _ptr;
		}

		T& operator*()
		{
			return *_ptr;
		}
	private:
		T* _ptr;
		int* _pcount;
	};
}

int main()
{
	s::Share_ptr<int> spt1(new int(1));
	cout << "spt1 pcount: " << spt1.use_count() << endl;
	s::Share_ptr<int> spt2(spt1);
	cout << "spt2 pcount: " << spt2.use_count() << endl;

	s::Share_ptr<int> spt3(new int(3));
	spt3 = spt1;
	cout << "spt3 pcount: " << spt3.use_count() << endl;

	return 0;
}

C++——智能指针,c++,c++,算法

简易auto_ptr实现思路:

  1. Shared_ptr在其内部,给每个资源都维护了着一份计数_pcount,用来记录该份资源被几个对象共 享。
  2. 在构造函数中获取资源,并且设置计数器_pcount为1,表示当前只有自己在管理该资源。
  3. 在拷贝构造函数中,要与传入的对象一起管理它所管理的资源,因此同时将该资源对应的计数器++
  4. 在拷贝赋值函数中,若在管理资源需要先放弃管理当前资源,即先--_pcount,然后与传入对象一起管理传入对象所管理的资源,即++ _pcount。
  5. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减 一。如果引用计数为0,说明自己是最后一个使用该资源的对象,这时候必须由自己释放资源;如果引用计数不为0,说明还有其他对象使用该资源,不能释放资源,否则其他对象就成野指针了
  6. *->运算符进行重载,使得对象具有像指针一样的行为。

为什么share_ptr的引用计数要放在堆上?

  • 该引用计数不能是内置函数成员int类型的数据,这样会导致每个对象都有一个引用计数,而当多个对象管理同一个资源时,这些对象所用的引用计数应该是同一个。

C++——智能指针,c++,c++,算法

  • 该引用计数也不能是静态变量,静态变量会造成所有share_ptr类型的对象所共用同一个引用计数,那么当管理不同资源时就出计数问题。

C++——智能指针,c++,c++,算法

  • 因此将shared_ptr的引用计数设置为一个指针,指针指向一个计数器,当一个资源第一次被管理时就去堆区开辟一块空间用于存储其对应的引用计数,而当前其他对象也管理该资源时,除了将该资源交给对象外,该指针也要交给它,其他对象就能拿到该引用计数。
  • 这时候管理同一个资源的多个对象所访问到的引用计数就是同一个,管理不同资源的对象所访问的引用计数不是同一个,等同于资源与其引用计数一一对应起来。

C++——智能指针,c++,c++,算法

std::shared_ptr的线程安全问题

  1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2。这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是线程安全的。
  2. . 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。

首先验证C++库里的shared_ptr

#include<iostream>
#include<vector>
#include<memory>
#include<mutex>
#include<thread>
using namespace std;
	struct Date
{
public:
	int _year = 0;
	int _month = 0;
	int _day = 0;
};
void test_shared_ptr1()
{
	int n = 50000;
	mutex mtx;
	std::shared_ptr<Date> sp1(new Date);

	thread t1([&]()
		{
			for (int i = 0; i < n; ++i)
			{
				std::shared_ptr<Date> sp2(sp1);
				//mtx.lock();
				sp2->_year++;
				sp2->_day++;
				sp2->_month++;
			//	mtx.unlock();
			}
		});

	thread t2([&]()
		{
			for (int i = 0; i < n; ++i)
			{
				std::shared_ptr<Date> sp3(sp1);
			//	mtx.lock();
				sp3->_year++;
				sp3->_day++;
				sp3->_month++;
			//	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;
}

int main()
{
	test_shared_ptr1();
}
  • 首先创建一个sp1对象,对象管理Date结构体,结构体里有三个成员变量,_year、 _month、 _day。
  • 在线程thread1里sp2对象对sp1对象进行拷贝构造,即sp2也同sp1一起管理同一个Date结构体,并且对里面的成员变量进行加加,循环500次;
  • 在线程thread2里sp3对象对sp1对象进行拷贝构造,即sp3也同sp1、sp2一起管理同一个Date结构体,并且对里面的成员变量进行加加,循环500次;
  • 即sp2和sp3两个对象总和对Date对象里的成员变量加加1000次,遍历完后可以看到三个参数都为1000(如下图)

C++——智能指针,c++,c++,算法

  • 经过多次实验,可以看到存在成员对象不为1000的结果,说明了shared_ptr存在线程安全问题。其根本在于多线程访问同一块资源时,可能存在多个线程同时对同一个成员变量进行自增或自减操作,而其++操作不具备原子性。因此需要借助锁,将++操作的代码划分为临界区,互斥锁将临界区保护起来,每次只允许一个线程进入临界区,访问临界资源。

C++——智能指针,c++,c++,算法

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

void test_shared_ptr1()
{
	int n = 500;
	mutex mtx;
	std::shared_ptr<Date> sp1(new Date);

	thread t1([&]()
		{
			for (int i = 0; i < n; ++i)
			{
				std::shared_ptr<Date> sp2(sp1);
				mtx.lock();
				sp2->_year++;
				sp2->_day++;
				sp2->_month++;
				mtx.unlock();
			}
		});

	thread t2([&]()
		{
			for (int i = 0; i < n; ++i)
			{
				std::shared_ptr<Date> sp3(sp1);
				mtx.lock();
				sp3->_year++;
				sp3->_day++;
				sp3->_month++;
				mtx.unlock();
			}
		});

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

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

	cout <<"sp1->_year: " << sp1->_year << endl;
	cout <<"sp1->_month: " << sp1->_month << endl;
	cout <<"sp1->_year: " << sp1->_day << endl;
}

int main()
{
	test_shared_ptr1();
}

加互斥锁解决shared_ptr的线程安全问题

要解决引用计数的线程安全问题,本质就是让对引用计数的自增和自减变成一个原子操作,因此对引用计数的操作进行加锁保护,将对引用计数的操作划分为临界区,每次只允许一个线程进入临界区做引用计数操作。

namespace s
{
	template<class T>
	class Share_ptr
	{
	public:
		Share_ptr(T* ptr = nullptr) :_ptr(ptr), _pcount(new int(1)),_mut(new mutex) {}

		Share_ptr<T>& operator=(const Share_ptr<T>& spt)//赋值重载
		{
			if (_ptr != spt._ptr)
			{
				Release();
				_ptr = spt._ptr;
				_pcount = spt._pcount;
                _mut = spt._mut;
				Addpcount();
			}
			return *this;
		}

		int use_count()
		{
			return *_pcount;
		}
		void Addpcount()
		{
			_mut->lock();
			(*_pcount)++;
			_mut->unlock();
		}
		Share_ptr(const Share_ptr<T>& spt)//拷贝构造
			:_ptr(spt._ptr)
			, _pcount(spt._pcount)
			,_mut(spt._mut)
		{
			Addpcount();
		}
		void Release()
		{
			bool flag = false;
			_mut->lock();
			if (--(*_pcount) == 0 && _ptr)
			{
				//计数为0,释放资源
				cout << "delete _ptr" << endl;
				delete _ptr;
				delete _pcount;
				flag = true;
			}
			_mut->unlock();
			if (flag == true)
			{
				delete _mut;
			}
		}
		~Share_ptr()
		{
			Release();
		}

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

		T* get()
		{
			return _ptr;
		}

		T& operator*()
		{
			return *_ptr;
		}
	private:
		T* _ptr;
		int* _pcount;
		mutex* _mut;
	};
}
  • 在shared_ptr类中新增互斥锁,为了让管理同一份资源的多个对象访问到的是同一个互斥锁,管理不同资源的对象访问到的不是同一个互斥锁,互斥锁需要在堆区创建。
  • 在拷贝构造函数和拷贝赋值函数中,除了将对应资源和引用计数交给当前对象时,还需要将其资源对应互斥锁也交给当前对象。
  • 当一个资源对应的引用计数为0时,,除了将对应资源和引用计数释放外,还需要将其资源对应的互斥锁释放。
  • 在拷贝构造函数、拷贝赋值函数、析构函数中需要对引用计数做自增或自减操作,可以将其操作封装为Addpcount函数、Release函数,然后只需要在这两个函数中进行加锁保护。
  • shared_ptr只需要保证引用计数的线程安全问题,而管理资源的线程安全问题由管理这块资源的操作者来保证

shared_ptr的循环引用问题

struct ListNode
{
	int _data=0;
	s::Share_ptr<ListNode> _prev;
	s::Share_ptr<ListNode> _next;
	~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
	s::Share_ptr<ListNode> node1(new ListNode);
	s::Share_ptr<ListNode> node2(new ListNode);
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	node1->_next = node2;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}

C++——智能指针,c++,c++,算法

  • 新建一个listNode对象,让shared_ptr类node1对象管理,此时node1对应的引用计数为1,即listNode资源被一个shared_ptr对象管理。
  • 新建一个listNode对象,让shared_ptr类node2对象管理,此时node2对应的引用计数为1,即listNode资源被一个shared_ptr对象管理。
  • 此时node1和node2的引用计数都为1,我们不需要手动释放资源。在退出栈帧的时候会调用析构函数进行资源释放

C++——智能指针,c++,c++,算法

  • 让node1的next指向node2,即node1和node2一起来管理node2所管理的资源,因此node2的use_count等于2。
  • 让node2的prev指针指向node1,即node1和node2一起来管理node1所管理的资源,因此node1的use_count等于2。

C++——智能指针,c++,c++,算法

  • 在程序结束时,会调用node1和node2的析构函数,此时node1的use_count减减等于1,node2的use_count减减等于1;但node1无法被释放,原因在于node2的prev指针指向node1,想要释放node1需要先释放node2的prev指针,释放node2的prev指针的前提是释放node2;而node2也无法被释放,原因在于node1的next指针指向node2,想要释放node2需要先释放node1的next指针,释放node1的next指针的前提是释放node1。因此造成了循环引用问题。

weak_ptr

weak_ptr的作用为:

  • 构造出来的weak_ptr对象与shared_ptr对象管理同一份资源,但不会增加这块资源对应的引用计数。
  • weak_ptr支持用shared_ptr对象来构造weak_ptr对象
  • weak_ptr不是用来管理资源释放的,它主要是用来解决shared_ptr的循环引用问题。
namespace t
{template<class T>
class weak_ptr
{
public:

	weak_ptr() :_ptr(nullptr) {}



	weak_ptr<T>& operator=( s::Share_ptr<T>& spt)//赋值重载
	{
		_ptr = spt.get();
		return *this;
	}

	weak_ptr(const s::Share_ptr<T>& spt)//拷贝构造
		:_ptr(spt.get())
	{}

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

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

private:
	T* _ptr;
};
}
  • 提供一个无参的构造函数。
  • 拷贝构造函数用shared_ptr构造weak_ptr,把shared_ptr的指针传递給weak_ptr,weak_ptr也能够管理shared_ptr所管理的对象。
  • 赋值重载函数用shared_ptr构造weak_ptr,把shared_ptr的指针传递給weak_ptr,weak_ptr也能够管理shared_ptr所管理的对象。
  • *->运算符进行重载,使得对象具有像指针一样的行为。
  • shared_ptr提供一个get函数,用于获取指针管理资源。
struct ListNode
{
	int _data = 0;
	t::weak_ptr<ListNode> _prev;
	t::weak_ptr<ListNode> _next;
	~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
	s::Share_ptr<ListNode> node1(new ListNode);
	s::Share_ptr<ListNode> node2(new ListNode);
	cout << " node1.use_count: " << node1.use_count() << endl;
	cout << " node2.use_count: " << node2.use_count() << endl;
	node1->_next = node2;
	node2->_prev = node1;
	cout << " node1.use_count: " << node1.use_count() << endl;
	cout << " node2.use_count: " << node2.use_count() << endl;
	return 0;
}

C++——智能指针,c++,c++,算法

可以看到此时资源正常释放了。

定制包装器

当智能指针的生命周期结束,所有的智能指针的析构函数都默认以delete的方式将资源释放。实际上这种方式并不好,因为不是所有智能指针所管理的资源都是以new的方式创建的。比如智能指针可能管理以new[]方式创建的资源,也管理的是一个文件指针。

int main()
{
	s::Share_ptr<int> sp(new int[10]);

	s::Share_ptr<int> sp1(fopen("test.cpp", "r"));
	return 0;
}
  • 在生命周期结束时,对于以new[]方式创建的资源进行delete会造成程序崩溃,因为以new[]申请到的资源必须以delete[]的方式释放。而以fopen打开的文件指针必须以fclose的方式进行关闭。

定制删除器的实现问题文章来源地址https://www.toymoban.com/news/detail-699701.html

  • C++标准库中实现shared_ptr时是分成了很多个类的,因此C++标准库中可以将删除器的类型设置为构造函数的模板参数,然后将删除器的类型在各个类之间进行传递。
  • 我们是直接用一个类来实现share_ptr,因此不能将定制删除器的类型设置为构造函数的模板参数。删除器需要在析构函数的Release函数中使用,因此需要用一个成员变量将器删除器保存下来,而在定义这个成员变量时就需要指定删除器的类型,因此就需要給shared_ptr类再增加一个模板参数,在构造shared_ptr对象时就指定删除器的类型。然后增加一个支持传入删除器的构造函数,在构造shared_ptr对象时就传递删除器,在释放资源时就调用该删除器即可。设置一个默认删除器,当用户定义shared_ptr对象时不传入删除器,就默认以delete的方式释放资源。
	template<class T>
	class default_delete
	{
	public:
		void operator()(T* ptr)
		{
			cout << "delete ptr" << endl;
			delete ptr;
		}
	};

	template<class T>
	class Delarry
	{
	public:
		void operator()( T* arr)
		{
			cout << "delete[]" << arr << endl;
			delete[]arr;
		}
	};

	template<class T,class D=default_delete<T>>
	class Share_ptr
	{
	public:
		Share_ptr(T* ptr ,D del) :_ptr(ptr), _pcount(new int(1)),_mut(new mutex),_del(del) {}
		void Release()
		{
			bool flag = false;
			_mut->lock();
			if (--(*_pcount) == 0 && _ptr)
			{
				//计数为0,释放资源
				cout << "delete _ptr" << endl;
				_del( _ptr);//调用对应制定删除器释放资源
				delete _pcount;
				flag = true;
			}
			_mut->unlock();
			if (flag == true)
			{
				delete _mut;
			}
		}
		~Share_ptr()
		{
			Release();
		}
        //......
      private:
		T* _ptr;
		int* _pcount;
		mutex* _mut;
		D _del;
class Fclose
{
public:
	void operator()(FILE* ptr)
	{
		cout << "fclose ptr" << endl;
		fclose(ptr);
	}
};

int main()
{
	s::Share_ptr<int,s::Delarry<int>> sp(new int[10],s::Delarry<int>());
	s::Share_ptr<FILE, function<void(FILE*)>> sp2(fopen("test.cpp", "r"), [](FILE* ptr) {
		cout << "fclose: " << ptr << endl;
		fclose(ptr); });
	return 0;
}

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中的实现的。

到了这里,关于C++——智能指针的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++学习】智能指针

    🐱作者:一只大喵咪1201 🐱专栏:《C++学习》 🔥格言: 你只管努力,剩下的交给时间! 如上图代码所示,在Func中开辟动态空间,在调用完Division函数后释放该空间。 如果Division没有抛异常,那么动态空间会被正常释放。 如果Division抛了异常,就会去匹配对应的catch,而Fu

    2024年02月06日
    浏览(57)
  • c++ 学习系列 -- 智能指针

    C++ 程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。但使用普通指针,容易造成内存泄露(忘记释放)、二次释放、程序发生异常时内存泄露等问题等。 另外,使用普通指针容易产生 野指针、悬空指针 等问题。 所以 C++11 就引入了智能指

    2024年02月13日
    浏览(59)
  • C++智能指针的发展

    GC–garbage collection垃圾回收,Java里的机制。在头文件 memory 中 堆内存泄漏(Heap leak) 堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那

    2023年04月19日
    浏览(37)
  • 【C++】智能指针详解

    今天我们来讲一下c++中的智能指针。 智能指针不是指针,是一个管理指针的类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。 动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源 C++ 98 中产

    2024年02月04日
    浏览(79)
  • 面试:C++ 11 智能指针

    内存泄露在维基百科中的解释如下: 在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了

    2024年02月07日
    浏览(43)
  • 【C++入门到精通】智能指针 [ C++入门 ]

    在C++编程中,内存管理一直是一个重要的话题。手动分配和释放内存可能会导致各种问题,例如内存泄漏和悬挂指针,这些问题往往会导致程序崩溃或产生不可预测的结果。为了解决这些问题, C++提供了一种称为智能指针的机制,它可以自动管理内存分配和释放,从而避免了

    2024年01月21日
    浏览(45)
  • C++知识点 -- 智能指针

    对于上面的场景,p1和p2在new申请空间后,div函数如果出现了除0错误,那么程序就会抛出异常,跳到接受异常的程序段继续执行,p1和p2申请的空间就没有被正常释放,造成了内存泄漏; 这种场景我们就可以使用智能指针来解决空间的释放问题。 RAII(Resource Acquisition Is Initia

    2024年02月03日
    浏览(61)
  • C++中常用的智能指针

    智能指针是一种C++语言中的类,可以像指针一样引用对象,但可以自动管理对象的内存。C++中有三种常用的智能指针:unique_ptr、shared_ptr和weak_ptr。 unique_ptr:是一种独占所有权的智能指针,即同一时间只能有一个unique_ptr指向某个对象。当unique_ptr超出作用域或被删除时,它所

    2024年02月01日
    浏览(43)
  • 面试—C++《智能指针》常考点

    目录 1.为什么需要智能指针 2. 内存泄漏 2.1 什么是内存泄漏,内存泄漏的危害 2.2 内存泄漏分类 2.3 如何检测内存泄漏 2.4如何避免内存泄漏 3.智能指针的使用及原理 3.3 std::auto_ptr 3.4 std::unique_ptr 3.5 std::shared_ptr  下面我们先分析一下下面这段程序有没有什么内存方面的问题?

    2023年04月11日
    浏览(34)
  • 【C++】智能指针(RAII)详解

      我们在上篇文章中(异常处理详解)提到了 RAII 。那么本篇文章会对此进行详解。重点是智能指针的详解。其中会讲解到 RAII 思想、auto_ptr、unique_ptr、shared_ptr、weak_ptr、循环引用问题 。希望本篇文章会对你有所帮助。 文章目录 一、为什么需要智能指针 二、智能指针的使用

    2024年02月10日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包