浅析C++标准库与boost库中的智能指针

本文深入探讨了智能指针的概念及其实现方式,包括AutoPtr、ScopedPtr和shared_ptr等,并介绍了它们如何帮助开发者管理内存资源,避免内存泄漏等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是智能指针呢,它是行为类似于指针的类对象,但这种对象还有其他功能。我们为什么要封装智能指针类对象呢?这是因为C++中的动态内存需要用户自己来维护,动态开辟的空间,在出函数作用域或者程序正常退出前必须释放掉,否则会造成内存泄漏,所以我们会定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。

我们可以来看下面一段程序,这里一段空间需要释放多次,程序会十分繁琐。并且若没有及时释放很容易出现资源泄露等问题。

#include<iostream>
using namespace std;

void FunTest1()
{
	throw 1;
}

bool FunTest2()
{
	return false;
}

void FunTest()
{
	int* p = new int[10];
	FILE* fp = fopen("1.txt", "rb");
	if(NULL == fp)
	{
		delete[] p;
		p = NULL;
		return;
	}

	try
	{
		FunTest1();
	}
	catch(...)
	{
		delete[] p;
		p = NULL;
		fclose(fp);
		throw;
	}

	if(!FunTest2())
	{
		delete[] p;
		p = NULL;
		fclose(fp);
		return;
	}

	delete[] p;
	p = NULL; 
	fclose(fp);
}

int main()
{
        FunTest();
	return 0;
}
首先我们来模拟实现C++标准库中的AutoPtr。

#include<iostream>
using namespace std;

//第一版本AutoPtr
template<typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap = 0)
		:_ptr(ap)
	{}

	AutoPtr(AutoPtr<T>& ap)//拷贝构造不能用const,因为后续要改变ap._ptr的值
		:_ptr(ap._ptr)//将ap._ptr的管理权交给类中_ptr
	{
		ap._ptr == NULL;//置空ap
	}

	AutoPtr<T>& operator=(AutoPtr<T>& ap)//赋值运算符重载同样不能用const修饰
	{
		if(this != &ap)
		{
			if(_ptr)
				delete _ptr;
				_ptr = ap._ptr;
				ap._ptr = NULL;
		}
		return *this;
	}

	~AutoPtr()
	{
		if(_ptr)
			delete _ptr;
		    _ptr = NULL;
	}
//基本操作
	T& operator*()
	{
		return *_ptr;
	}

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

private:
	T* _ptr;
};
int main()
{
	AutoPtr<int> ap1(new int(3));
	AutoPtr<int> ap2(ap1);//ap1将资源管理权限交给ap2
	*ap2 = 1;
	*ap1 = 2;//ap1已经将管理权限交出,此时对ap1进行解引用会出错
	system("pause");
	return 0;
}
接下来来实现第二版本AutoPtr。

#include<iostream>
using namespace std;

//第二版本AutoPtr
template<typename T>
class AutoPtr
{
public:
	AutoPtr(T* ap)
		:_ptr(ap)
		,_owner(true)//若资源由自己管理则_owner为true
	{
		if(_ptr==NULL)
			_owner =  false;
	}

	AutoPtr(const AutoPtr<T>& ap)//可以由const修饰,因为指针的指向并不改变,只是将其_owner置为false
	{
		_ptr = ap._ptr;
		ap._owner = false;
	}

	AutoPtr<T>& operator=(const AutoPtr<T>& ap)//同样有const修饰
	{
		if(this != &ap)
		{
			delete _ptr;
			_ptr = ap._ptr;
			_owner = true;
			ap._ptr = NULL;
			ap._owner = false;
		}
		return *this;
	}

	~AutoPtr()
	{
		if(_owner)//对这块资源有管理权限才可以释放
			delete _ptr;
	}
//对指针的基本操作
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	mutable bool _owner;//可变的
};

int main()
{
	AutoPtr<int> ap1(new int);
	AutoPtr<int> ap2(ap1);
	*ap1 = 1;
	*ap2 = 2;
	system("pause");
	return 0;
}
上面这两个AutoPtr虽然第二个比第一个好些,但是指针都指向同一段空间,如果在函数结束释放空间则后续的访问也会出错。

所以又提出了ScopedPtr,这个智能指针采取比较暴力的手段,让空间只由自己一个来管理。

我们可以看一下它是如何实现的。

//防拷贝
#include<iostream>
using namespace std;

template<class T>
class ScopedPtr
{
public:
	ScopedPtr(T* sp)
		:_ptr(sp)
	{}

	~ScopedPtr()
	{
		if(_ptr)
			delete _ptr;
		_ptr = NULL;
	}

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

	T* operator->()
	{
		return _ptr;
	}
	
private:
	ScopedPtr(ScopedPtr<T>& sp);//此处拷贝构造函数与赋值运算符重载都要设置成私有声明
	ScopedPtr<T>& operator=(ScopedPtr<T>& sp);
	T* _ptr;
};
在这里就需要介绍一下如何防拷贝,防拷贝有三种方法:

1.声明成公有,这样的话类外的人可以给出定义。

2.声明成私有,但是把定义也给出,这种情况下友元函数可以调用,类内成员函数也可能调用。所以也不予采用。

3.声明成私有,这种方法简洁并且也能起到防拷贝的作用

接着我们继续模拟实现ScopedArray:

template<typename T>
class ScopedArray
{
public:
	ScopedArray(T* s)
		:_ptr(s)
	{}

	~ScopedArray()
	{
		if(_ptr)
			delete[] _ptr;
	}

	T& operator[](int index)
	{
		return _ptr[index];
	}
private:
	ScopedArray(const ScopedArray<T>& psa);
	ScopedArray<T>& operator=(ScopedArray<T> psa);
	T* _ptr;
};
但是库里面并没有引进ScopedArray这是因为它类似于我们库中的Vector数组,都是以顺序方式存储数据。

最后我们来模拟boost库中shared_ptr,但这个函数线程不是很安全,一般建议使用scoped_ptr

#include<iostream>
using namespace std;

template<typename T>
class SharedPtr
{
public:
	SharedPtr(T* sp = 0)
		:_ptr(sp)
		,_count(new int(1))
	{}
	SharedPtr(const SharedPtr<T>& sp)
		:_ptr(sp._ptr)
		,_count(sp._count)
	{
		++(*_count);
	}
	SharedPtr<T>& operator=(const SharedPtr<T>& sp)
	{
		if(this != &sp)
		{
			if(0 == --(*_count) && _ptr)
			{
				delete _ptr;
			    _ptr = NULL;
				delete _count;
				_count = NULL;
			}
			_ptr = sp._ptr;
			_count = sp._count;
			++(*_count);
		}
		return *this;
	}
	~SharedPtr()
		{
			if(_ptr && 0 == --(*_count))
			{
				delete _ptr;
				_ptr = NULL;
				delete _count;
				_count = NULL;
			}
		}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _count;//开辟引用计数空间
};
int main()
{
	SharedPtr<int> sp1(new int(1));
	SharedPtr<int> sp2(sp1);
	SharedPtr<int> sp3;
	sp3 = sp2;
	*sp1 = 2;
	*sp2 = 3;
	*sp3 = 4;
	system("pause");
	return 0;
}

//定制删除器
#include<boost/shared_ptr.hpp>
using namespace boost;

class FClose
{
public:
	void operator()(void* fp)
	{
		fclose((FILE*)fp);
	}
};

class Free
{
public:
	void operator()(void* p)
	{
		free(p);
	}
};
void FunTest()
{
	shared_ptr<FILE> p1(fopen("test.txt","r"),FClose());
	shared_ptr<int> p2((int*)malloc(sizeof(int)),Free());
}
int main()
{
	FunTest();
	system("pause");
	return 0;
}

后面还有比较重要的循环引用等问题将在下一篇博客介绍。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值