c++:智能指针(c++11版本unique_ptr、shared_ptr、weak_ptr)(二)

从前面的内容可以看出auto_ptr具有比较多的缺陷,使用时容易出错。在C++ 11标准中出现了新的智能指针unique_ptr、 shared_ptr与weak_ptr等

一、unique_ptr

特点:所有权唯一,禁止拷贝和权限转移

缺点:unique_ptr是简单粗暴的防止拷贝,这种比较简单,效率高,但是功能不全面,不支持拷贝和赋值操作。无法共享数据,可自主实现数据共享,但在释放时会导致堆内存重复释放导致系统崩溃。

下面是unique_ptr的实现:

#include<iostream>
#include<memory>

template<typename T>
class Unique_Ptr
{
public:
	Unique_Ptr(T* ptr)
		:mptr(ptr)
	{}
	Unique_Ptr()
	{
		delete mptr;
		mptr = NULL;
	}
	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()
{
	int* p = new int;
	Unique_Ptr<int> up1(p);
	Unique_Ptr<int> up2(p);
	Unique_Ptr<int> up3(p);
	return 0;
}

1.禁止赋值和复制

unique_ptr禁止赋值和复制,“唯一”地拥有其所指对象,同一时刻只能有一个unique_ptr实例指向给定对象。也就是说模板类unique_ptr的copy构造函数以及等号(“=”)操作符是无法使用的。 
通过禁止复制和赋值可以较好的改善auto_ptr的所有权转移问题。下面是其禁止复制和赋值的例子:
 

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

void Fun1( unique_ptr<int> up )
{
}

int main()
{
    unique_ptr<int> up1 = unique_ptr<int>(new int(10));

    //不允许复制,所以以下三个均错误
    unique_ptr<int> up2 = up1;  // error
    unique_ptr<int> up3(up1);   // error
    Fun1(up1);                  // error

    //不允许赋值('='),所以下面错误
    unique_ptr<int> up4;
    up4 = up1;                  // error

    return 0;
}

2.针对auto_ptr缺陷的改善

1) 管理数组指针

 因为unique_ptr有unique_ptr< X[ ] >重载版本,销毁动态对象时调用delete[],所以可以用unique_ptr来管理数组指针。

unique_ptr< Test[ ] > uptr1(new Test[3]);
//注意 unique_ptr<Test> uptr3(new Test[3]);是不对的
unique_ptr<int[]> uptr2(new int[5]);

2)做容器(vector, list, map)元素

vector<unique_ptr<int> > vec;
unique_ptr<int> ptr1(new int(3));
vec.push_back(std::move(ptr1));
//vec.push_back(ptr1); //由于禁止复制这样不行

二、shared_ptr

特点:shared_ptr的实现原理是通过引用计数来实现,拷贝或赋值时将引用计数加1,析构时只有当引用计数减到0才释放空间,否则只需将引用计数减1即可.shared_ptr共享所有权。

缺点:相互引用  内存泄漏

如图理解引用计数和shared_ptr的关系:

 

#include<memory>

class Ref_Man   //引用计数
{
public:
	static Ref_Man* getInstance()
	{
		if (prm == NULL)
		{
			prm = new Ref_Man();
		}
		return prm;
	}
	int getRef(void* ptr)       
	{
		if (ptr == NULL)
		{
			return -1;
		}
		int index = find(ptr);
		if (index != -1)
		{
			return ref_count[index].count;
		}
		return -1;
	}
	void addRef(void* ptr)
	{
		if (ptr == NULL)
		{
			return;
		}
		int index = find(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 = find(ptr);
		if (index != -1)
		{
			if (getRef(ptr) != 0)
			{
				--ref_count[index].count;
			}
		}
	}
private:
	Ref_Man()
	{
		cursize = 0;
	}
	Ref_Man(const Ref_Man&);
	int find(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 Ref
	{
		void* addr;    //堆内存地址
		int count;     //堆内存所对应的指针数目
	}Ref;
	Ref ref_count[10];
	int cursize;
	static Ref_Man* prm;
};
Ref_Man* Ref_Man::prm = NULL;

template<typename T>
class Shared_Ptr
{
public:
	Shared_Ptr(T* ptr = NULL)     //构造
		:mptr(ptr)
	{
		prm->addRef(mptr);
	}
	Shared_Ptr(Shared_Ptr<T>& rhs)    //拷贝构造
	{
		mptr = rhs.mptr;
		prm->addRef(mptr);
	}
	Shared_Ptr<T>& operator=(Shared_Ptr<T>&rhs)   //赋值
	{
		if (this != &rhs)
		{
			prm->delRef(mptr);
			if (prm->getRef(mptr))
			{
				delete mptr;
			}
			mptr = rhs.mptr;
			prm->addRef(mptr);
		}
		return *this;
	}
	~Shared_Ptr()
	{
		prm->delRef(mptr);  //--引用计数
		if (prm->getRef(mptr) == 0)   //获取mptr引用计数,如果为0释放
		{
			delete mptr;
		}
		mptr = NULL;
	}
	T& operator*()    //重载*
	{
		return *mptr;
	}
	T* operator->()    //重载->
	{
		return mptr;
	}
private:
	T* mptr;
	static Ref_Man* prm;
};
template<typename T>
Ref_Man* Shared_Ptr<T>::prm = Ref_Man::getInstance();

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

int main()
{
	std::shared_ptr<A> pa(new A());  
	std::shared_ptr<B> pb(new B());
	pa->spa = pb;   //相互引用
	pb->spb = pa;
	return 0;
}

int main()
{
	int* p = new int;           //使用weak_ptr后解决了相互引用导致内存泄漏的问题
	Shared_Ptr<int> sp1(p);
	Shared_Ptr<double> spd = new double;
	Shared_Ptr<int> sp2(p);
	Shared_Ptr<int> sp3(p);
	return 0;
}

shared_ptr相互引用问题

 

  1. pa和pb两个智能指针对象分别指向spa和spb,引用计数变成1.
  2. pa的spa指向pb,pb的spb指向pa,引用计数变成2。
  3. pa和pb析构,引用计数减到1,但是spa还指向spb。但是spb还指向spa。
  4. 也就是说spa析构了,pb就释放了。
  5. 也就是说spb析构了,pa就释放了。
  6. 但是spa属于pa的成员,pa释放了,spa才会析构,而pa由spb管理,spb属于pb成员,所以这就叫循环引用,谁也不会释放。 

weak_ptr的出现,很好的解决了这个问题。

 三、weak_ptr

特点:weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它没有重载operator*和->,故而不具有普通指针的行为。它的最大作用在于协助shared_ptr工作。 
weak_ptr被设计为与shared_ptr共同工作,可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。
作用:解决强智能指针shared_ptr相互引用的问题。

代码如下:

 

​template<typename T>
Ref_Man* Shared_Ptr<T>::prm = Ref_Man::getInstance();

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

int main()
{
	std::shared_ptr<A> pa(new A());
	std::shared_ptr<B> pb(new B());
	pa->spa = pb;
	pb->spb = pa;
	return 0;
}


int main()
{
	int* p = new int;
	Shared_Ptr<int> sp1(p);
	Shared_Ptr<double> spd = new double;
	Shared_Ptr<int> sp2(p);
	Shared_Ptr<int> sp3(p);
	return 0;
}​

因为在执行pa->spa = pb;pb->spb = pa;时weak_ptr的spa和spb不会增加pa和pb的引用计数,这样就保证了正常的释放。

友情链接:

C++智能指针梳理

C++:RAII思想和智能指针(auto_ptr,unique_ptr,shared_ptr)详解了引用计数部分,并且提供了线程安全的shared_ptr,很值得学习哦~

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值