智能指针

本文深入探讨C++中智能指针的概念与实现,包括auto_ptr、unique_ptr、shared_ptr和weak_ptr,解析它们如何有效管理内存,防止内存泄漏,以及在交叉引用场景下如何配合使用。

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

在使用C++的过程中我们对内存的管理是一件让人很头疼的事情。在使用new和malloc的过程中,我们往往会忘记调用delete或free去释放已经不再所使用的堆内存,导致了内存泄露的情况。内存泄露会造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

为了能保证手动申请的内存都能够被释放,C++中就引入了智能指针这个概念。我们知道在C++中对象的生成会调用构造函数,当一个对象的生存周期结束时,系统会调用析构函数去释放它所占有的资源。智能指针就是利用了这个特性,用对象来代替指针。在对象的生存周期结束时就可以让系统去释放申请的堆内存。

在C++11版本之前C98版本只有一个智能指针auto_ptr。到了C++11版本之后,又新引进了三种智能指针unique_ptr、shared_ptr、weak_ptr,这四种智能指针各不相同,各有特点,现在auto_ptr已经基本被摒弃了,不在使用。

一、auto_ptr

在使用智能指针的过程中,如果多个智能指针指向同一块堆内存,如果这种情况不加以处理,就会造成多次释放同一块堆内存,auto_ptr采用了所有权唯一的方法,它指定新的智能指针取代旧智能指针的所有权。即赋值或者拷贝会导致旧的智能指针失效。

#include<iostream>

template<typename T>
class Auto_ptr
{
public:
	Auto_ptr(T* ptr):mptr(ptr){
		std::cout<<"Auto_ptr(T* ptr)"<<std::endl;
	}
	~Auto_ptr()
	{
		delete mptr;
		mptr = NULL;
		std::cout<<"~Auto_ptr(T* ptr)"<<std::endl;
	}
	Auto_ptr(Auto_ptr<T>& rhs)
	{
		mptr = rhs.Release();
		std::cout<<"Auto_ptr(Auto_ptr<T>& rhs)"<<std::endl;
	}
	Auto_ptr<T>& operator=(Auto_ptr<T>& rhs)
	{
		if(this!= &rhs)
		{
			delete mptr;
			mptr = rhs.Release();
		}
		return *this;
	}
	T& operator*()
	{
		return *mptr;
	}
	T* operator->()
	{
		return mptr;
	}
private:
	T* Release()
	{
		T* tmp = mptr;
		mptr = NULL;
		return tmp;
	}
	T* mptr;
};

int main()
{
	Auto_ptr<int> p1(new int);
	Auto_ptr<int> p2(p1);
	int *p = new int;
	Auto_ptr<int> p3(p);
	return 0;
}

二、unique_ptr

相同的问题,unique_ptr为了使所有权唯一,它禁止智能指针使用拷贝构造和赋值(赋值运算符的重载)。

template<typename T>
class Unique_ptr
{
public:
	Unique_ptr(T* ptr):mptr(ptr){
		std::cout<<"Unique_ptr(T*)"<<std::endl;
	}
	~Unique_ptr()
	{
		delete mptr;
		mptr = NULL;
		std::cout<<"~Unique_ptr()"<<std::endl;
	}
	T& operator*()
	{
		return *mptr;
	}
	T* operator->()
	{
		return mptr;
	}

private:
	/*
	将拷贝构造函数和赋值运算符的重载函数的声明写成私有,可以防止调用
	*/
	Unique_ptr(Unique_ptr<T>&);  
	Unique_ptr<T> operator=(Unique_ptr<T>&);
	T* mptr;
};

int main()
{
	Unique_ptr<int> p1(new int);
	return 0;
}

三、shared_ptr

shared_ptr,强智能指针,使用时需要包含头文件<memory>,它采用了引用计数的办法共享了空间的所有权,每多一个指针指向这一块内存,就将这一块内存的引用计数加1,当指针的生存周期结束时,先将这块内存的引用计数减1,再查看这个指针指向空间的引用计数是否为0,若为0,则释放这块空间,反之,不用释放。

#include<iostream>

//引用计数器类
class Ref_manage
{
public:
	static Ref_manage* GetInstance()
	{
		if(prm == NULL)
			prm =new Ref_manage();
		return prm;
	}
	void addref(void* ptr)
	{
		if(ptr == NULL)
			return;
		int index = findref(ptr);
		if(index != -1)
		{
			ref_count[index].count++;
		}
		else
		{
			ref_count[cursize].addr = ptr;
			ref_count[cursize++].count = 1;
		}
	}
	void delref(void* ptr)
	{
		if(ptr == NULL)
			return;
		int index = findref(ptr);
		if(index != -1)
		{
			if(getref(ptr) != 0)
			{
				ref_count[index].count--;
			}
		}
	}
	int getref(void* ptr)
	{
        if(ptr == NULL)
            return -1;
		int index = findref(ptr);
		if(index == -1)
		{
			throw std::exception("ptr is error");
		}
		return ref_count[index].count;
	}
private:
	Ref_manage()
	{
		cursize = 0;
	}
	Ref_manage(const Ref_manage& );
	int findref(void* ptr)
	{
		int index = -1;
		for(int i = 0;i<cursize;i++)
		{
			if(ref_count[i].addr == ptr)
			{
				index = i;
				break;
			}
		}
		return index;
	}
	typedef struct
	{
		void* addr;
		int count;
	}Ref;
	Ref ref_count[10];
	int cursize;
	static Ref_manage* prm;
};
Ref_manage* Ref_manage::prm = NULL;


//Shared_ptr
template <typename T>
class Shared_ptr
{
public:
	Shared_ptr(T* ptr)
	{
		mptr = ptr;
		rm->addref(mptr);
		std::cout<<"Shared_ptr(T*)"<<std::endl;
	}
	Shared_ptr(Shared_ptr<T>& rhs)
	{
		mptr = rhs.mptr;
		rm->addref(mptr);
		std::cout<<"Shared_ptr(Shared_ptr<T>& rhs)"<<std::endl;
	}
	Shared_ptr<T>& operator=(Shared_ptr<T>& rhs)
	{
		std:;cout<<"Shared_ptr<T>& operator=(Shared_ptr<T>&)"<<std::endl;
		if(this != &rhs)
		{
			rm->delref(mptr);
			if(rm->getref(mptr) == 0)
			{
				delete mptr;
			}
			mptr = rhs.mptr;
			rm->addref(mptr);
		}
		return *this;
	}
	~Shared_ptr()
	{
		std::cout<<"~Shared_ptr()"<<std::endl;
		rm->delref(mptr);
		if(rm->getref(mptr) == 0)
		{
			delete mptr;
		}
		mptr = NULL;
	}
	T& operator*()
	{
		return *mptr;
	}
	T* operator->()
	{
		return mptr;
	}
private:
	T* mptr;
	static Ref_manage* rm;
    template< typename E>
	friend class Weak_ptr;
};
template<typename T>
Ref_manage* Shared_ptr<T>::rm = Ref_manage::GetInstance();



int main()
{
	Shared_ptr<int> p1(new int);
	Shared_ptr<int> p2(p1);
	Shared_ptr<int> p3 = p2;
	return 0;
}

shared_ptr智能指针不仅可以解决内存泄露的问题,巧妙的处理了当多个地方需要共享一块内存时这块内存的释放问题。因为它有引用计数的机制,这就能使他在多个地方同时使用这块内存时,还能知道此时有多少人还在使用着这块内存,当没人使用的时候它才会释放这块内存。

虽然shared_ptr智能指针已经看似完美的解决了大部分问题。但还有一个问题是shared_ptr指针不能解决的,那就是当多个shared_ptr指针相互交叉引用的问题。

#include<iostream>
#include<memory>

class B;
class A
{
public:
	A()
	{
		std::cout<<"A::A()"<<std::endl;
	}
	~A()
	{
		std::cout<<"A::~A()"<<std::endl;
	}
	Shared_ptr<B> spa;
};
class B
{
public:
	B()
	{
		std::cout<<"B::B()"<<std::endl;
	}
	~B()
	{
		std::cout<<"B::~B()"<<std::endl;
	}
	Shared_ptr<A> spb;
};
int main()
{
	Shared_ptr<A> p1(new A);
	Shared_ptr<B> p2(new B);
	p1->spa = p2;
	p2->spb = p1;
	return 0;
}

我们可以看到直到程序运行结束,也没有释放掉A和B的空间,造成了内存泄露。原因如下:

图2
图1 

 

如图1所示,创建了两个shared_ptr指针p1和p2 ,分别指向个内存块A和B,A对象中有一个B对象的shared_ptr指针spa,B对象中有一个A对象的shared_ptr指针spb。此时A内存和B内存的引用计数都为1.

如图2所示,将A中的spa指向对象B,将B中的spb指向对象A,此时A和B内存的引用计数都为2.

图3

如图3所示,p1和p2的生存周期结束,调用析构函数,将A和B的引用计数分别减1,此时内存A和内存B的引用计数都为1,shared_ptr不会去释放内存,程序结束退出,造成了内存泄露。

针对于这种问题,于是就引入了weak_ptr。

四、weak_ptr

弱智能指针weak_ptr一般结合强智能指针来使用,它更像是强智能指针shared_ptr的一个助手。它并不具备普通指针的行为,并没有重载operator* 和operator ->,它最大的作用就是协助shared_ptr工作,像旁观者一样观察资源的使用情况。

weak_ptr与shared_ptr指向同一块空间时,weak_ptr的指向并不会增加引用计数器。并且weak_ptr也不能单独使用(它并为重载*运算符和->运算符)。

//weak_ptr
template <typename T>
class Weak_ptr
{
public:
	Weak_ptr(T* ptr = NULL):mptr(ptr){}
	Weak_ptr& operator=(Shared_ptr<T>& rhs)
	{
		mptr = rhs.mptr;
		return *this;
	}
private:
	T* mptr;
};


//交叉引用问题
class B;
class A
{
public:
	A()
	{
		std::cout<<"A::A()"<<std::endl;
	}
	~A()
	{
		std::cout<<"A::~A()"<<std::endl;
	}
	Weak_ptr<B> spa;
};

class B
{
public:
	B()
	{
		std::cout<<"B::B()"<<std::endl;
	}
	~B()
	{
		std::cout<<"B::~B()"<<std::endl;
	}
	Weak_ptr<A> spb;
};

int main()
{
	Shared_ptr<A> p1(new A);
	Shared_ptr<B> p2(new B);
	p1->spa = p2;
	p2->spb = p1;
	return 0;
}

我们可以看到weak_ptr和shared_ptr相互结合使用,就可以避免这种内存泄露问题。这是因为在将shared_ptr的指向赋值给weak_ptr时并不增加引用计数的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值