C++智能指针

大家都知道C/C++中对内存进行动态申请的时候, 堆上开辟的空间一定要进行释放, 否则造成了内存泄漏是一件十分危险的事情, 但是我们在平时写代码的时候, 难免会忘记释放空间, 或者当程序抛出异常的时候, 释放空间的代码被跳过, 执行到, 这都是造成内存泄漏的原因. 为了解决这一情况, C++中引入了智能指针,来对动态开辟的内存进行管理.

RAII思想

RAII的思想是指, 利用对象的生命周期来对程序的资源进行管理, 即对一个泛型的指针进行封装, 以一个对象的身份来管理动态开辟的空间, 而不单单是一个指针的身份.
当我们要进行动态内存的开辟是, 可以创建一个只能指针的对象, **在该对象的构造函数中进行空间的申请, 在对象的析构函数中进行空间的释放.**这样不仅解决了不需要用户手动释放空间的问题, 还保证了, 资源在对象的生命周期内始终有效.

接下来实现一个简单的实现RAII思想的指针

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

void fun(){
	int* p=new int(4);
	vector<int> v(100000000000,0);
	delete p;
}

int main(){
	try{
		fun();
	}
	catch(const exception& e){
		cout<<e.what()<<endl;
	}
	catch (...){
		cout<<"其他异常"<<endl;
	}
	return 0;
}

上述程序中, 由于vector的开辟长度太长, 当层序运行到此处是,就会抛出异常, 程序就会转调到main程序中的捕获异常出, 但是fun函数中的对P指针的释放就没有执行到, 这样就造成了内存泄漏.

我们将fun函数改为如下程序

void fun(){
	AutoPtr<int> p(new int(2));
	vector<int> v(10000000000000,0);
}

此时即使我们的fun函数中抛出异常, 但是当程序跳转到main函数中的时候, 也就是fun函数生命周期结束的时候, 会调用p对象的析构, 从而对空间进行释放.
C++智能指针_第1张图片
注意 : 只能指针在进行使用时, 必须保证开辟空间与常见对象在同一步进行, new操作符返回的是一个指针类型, 因此直接在对象的构造中使用new操作符

当然, 只能指针的实现,还包括了指针的功能

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

	T& operator*(){
		return *_ptr;
	}
	T* operator->(){
		return _ptr;
	}
private:
	T* _ptr;
};
int main(){
	AutoPtr<int> p(new int(1));
	AutoPtr<int> p1(p);
	return 0;
}

注意 : 既然只能实现指针的基本操作,那么就也有指针的拷贝与赋值操作, 指针的拷贝调用的是类的默认拷贝构造, 而默认拷贝构造只是一个浅拷贝, 浅拷贝只拷贝值, 不会拷贝对象中的空间, 因此在执行上述代码后,就会造成两个指针指向同一块空间, 这样当main函数的生命周期执行完之后, 在对 P ,P1进行洗后的时候, 会对同一块空间释放两次, 这样回导致程序的崩溃

auto_ptr

在STL中的auto_ptr是最早的一款智能指针, 它对指针的拷贝问题的解决是, 永远保证只有一个智能指针指向一个空间, 当对当前智能指针进行拷贝的时候, 就将原来的指针置空.
在上述的 AutoPtr中对构造函数与赋值运算符重载进行修改

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

	AutoPtr(AutoPtr<T>& p){
		_ptr=p._ptr;
		p._ptr=nullptr;
	}
	AutoPtr<T>& operator=(AutoPtr<T>& p){
		if(this!=&p ){
			if(_ptr!=nullptr){
				delete _ptr;	
			}
			_ptr=p._ptr;
			p._ptr=nullptr;
		}
	}
	T& operator*(){
		return *_ptr;
	}
	T* operator->(){
		return _ptr;
	}
private:
	T* _ptr;
};

注意 : 由于auto_ptr只能有一个智能指针指向空间, 因此auto_ptr是一个不完善的智能指针, 因此禁止使用

unique_ptr

由于auto_ptr的不完备性, 容易造成空间的二次释放, 因此c++11中有提供了一个unique_ptr, 他处理问题的方法很简单, 直接将拷贝构造与赋值运算符重载封死, 就最简单的解决了问题.

template<class T>
class UniquePtr{
public:
	UniquePtr(T* ptr)
		:_ptr(ptr)
	{
		cout<<"AutoPtr()"<<endl;
	}
	~UniquePtr(){
		if(_ptr!=nullptr){
			delete _ptr;
			_ptr=nullptr;
		}
		cout<<"~AutoPtr()"<<endl;
	}
	T& operator*(){
		return *_ptr;
	}
	T* operator->(){
		return _ptr;
	}
		
	UniquePtr(AutoPtr<T>& )=delete;
	operator=(AutoPtr<T>& )=delete;
private:
	T* _ptr;
};

shared_ptr

在C++11中还有一个更加全面的智能指针 sheard_ptr 它实现了前面俩个智能指针不能实现的拷贝与赋值.
它的原理是, 在智能指针的内部增加一个计数器, 当有多个智能指针指向一块空间是, 计数器就会加一, 当执行指正对象执行析构的时候, 先对计数器进行减一操作, 之后在判断计数器的结果是否为零, 如果为零,则对空间进行释放, 不为零则不作操作.

template<class T>
class SharedPtr{
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		,_count(new int(1));
	{}
	~SharedPtr(){
		(*_count)--;
		if(*_count==0){
			delete _ptr;
			_ptr=nullptr;
		}
	}
	SharePtr(SharePtr<T>& p)
		;_ptr(p._ptr)
		,_count(p._count)
	{
		(*_count)++;
	}
	SharePtr<T>& operator=(SharePtr<T>& p){
		if(this!=&p){
			(*_count)--
			if(*_count==0){
				delete _ptr;
				delete _count;
			}
			_ptr=p._ptr;
			_count=p._count;
		}
		(*_count)++;
	}
	T& operator*(){
		return *_ptr;
	}
	T* operator->(){
		return _ptr;
	}
	int GitCount(){
		return *_count;
	}
private:
	T* _ptr;
	int* _count;
};

注意:在shared_ptr的赋值运算符操作中, 赋值操作是将一个指针替换成另一个, 所以是要对指针记性判空, 删除原来的值,在进行赋值, 而在shared_ptr中, 就需要先判断_count的值是否是0 如果是0 则delete 之后赋值, 如果不是, 则直接改变智能指针内部的成员的值即可

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