C++智能指针的使用和仿写(auto_ptr、unique_ptr、shared_ptr、weak_ptr)(详解)

本博客主要记录以下内容:
1.auto_ptr(包括使用讲解和仿写)
2.unique_ptr(包括使用讲解和仿写)
3.shared_ptr(包括使用讲解和仿写)
4.weak_ptr(包括使用讲解和仿写)

一、auto_ptr

1.auto_ptr的使用和讲解

使用示例及讲解:

int main()
{
	int* p = new int(10);
	//auto_ptr<int> a_ptr1 = p;//不允许让普通指针为实参,进行隐式构造
	auto_ptr<int> a_ptr1(p);
	//delete p;//error 这句代码没错,会执行,但是对象a_ptr1的生存期到了之后调用析构函数时,会崩溃
	//原因是p和a_ptr1指向同一个堆空间,delete p释放了该空间,但是a_ptr1的析构函数会对该空间进行再次释放,就会崩溃
	//如下a_ptr1.get()是获取对象a_ptr1的指针成员变量
	cout << "a_ptr1拥有的堆空间的首地址:" << a_ptr1.get() << "\t        "  << "p指向的堆空间的首地址:" << p << endl;
	//发现a_ptr1和p指向同一个堆空间,所以为了避免重复释放堆空间引起的崩溃问题,一般会执行以下操作:
	//auto_ptr<int>a_ptr1(p);	p = NULL;即让裸指针失去对堆空间的拥有权

	auto_ptr<int>a_ptr2 = a_ptr1;//允许隐式构造,a_ptr1的资源被转移给a_ptr2,a_ptr1失去对资源的拥有权
	//auto_ptr<int>a_ptr3(a_ptr1);//用这句代替上句代码当然也可以,这个是显示构造
	cout << "a_ptr1拥有的资源的首地址:" << a_ptr1.get() << "\t        " << "a_ptr2拥有的资源的首地址:" << a_ptr2.get() << endl;
	//发现a_ptr2拥有的资源的首地址就是一开始的时候a_prt1拥有的资源的首地址
	//执行这句操作后,发现对象a_ptr1不再对资源有拥有权,a_ptr1的指针成员变量指向NULL
	//所以这句代码是将a_ptr1拥有的资源转让给a_ptr2,a_ptr1失去对资源的拥有权,指针成员变量指向NULL

	//auto_ptr<int>a_ptr3(p);//这句代码语法没错,但是这样会造成a_ptr3也指向p指向的资源的首地址
	//但是a_ptr2也指向这块资源,这样的话a_ptr2和a_ptr3就会指向同一个资源
	//对象生存期到了之后a_ptr2和a_ptr3都会调用析构函数,都会对拥有的资源进行delete释放,会引发对一个堆空间多次释放的问题,会导致程序崩溃
	//所以为了从源头避免这个问题,当用p构造一个auto_ptr指针对象之后,就应该立即执行p = NULL,让p失去对资源的拥有权


	//成员函数的使用
	int* pp = a_ptr2.get();//a_ptr2.get()是将a_ptr2的成员指针变量以临时指针变量的形式返回
	cout << "pp指向的堆空间的首地址:" << pp << endl;//这个时候pp存放的堆空间的首地址和a_ptr2的拥有的资源的首地址一样
	//a_ptr2.reset();//将a_ptr2拥有的资源释放,a_ptr2的成员指针变量指向NULL
	int* q = a_ptr2.release();//返回资源的首地址,将a_ptr2的指针成员变量指向NULL(注意:只是a_ptr2失去对资源的拥有权,但是并没有释放资源)
	cout << "a_ptr2的成员指针变量指向的空间:" << a_ptr2.get() << "\t" << "q指向的资源的首地址:" << q << endl;
	cout << "q指向的资源的内容:" << *q << endl;//输出10
	//没有对bool运算符的重载,后面讲述的指针有对bool运算符的重载
	
	
	cout << "/" << endl;
	int* x = new int(100);
	auto_ptr<int>a_p1(x);
	cout << "a_p1拥有的资源的首地址:" << a_p1.get() << endl;

	int* y = new int(200);
	auto_ptr<int>a_p2(y);
	cout << "a_p2拥有的资源的首地址:" << a_p2.get() << endl;

	a_p1 = a_p2;
	cout << "a_p1拥有的资源的首地址:" << a_p1.get() << endl;
	cout << "a_p2拥有的资源的首地址:" << a_p2.get() << endl;
	cout << "a_p1原本的资源的内容:" << *x << endl;//会输出随机值,因为原本的资源已经被释放了
	//会先对a_p1拥有的资源进行释放,然后将a_p2的资源转让给a_p1
	//然后让a_p2的成员指针变量指向NULL,即让a_p2失去对资源的拥有权
	


	//对智能指针进行仿写的时候,发现我一直以来的错误观点
	//我一直以为对一个指向NULL的指针delete会崩溃,后来发现对一个指向NULL的指针delete,不会发生错误
}

运行结果:
在这里插入图片描述

auto_ptr总结:

关于构造和赋值:
(1)不允许以裸指针为实参对auto_ptr指针进行隐式构造,只允许裸指针对auto_ptr进行显示的构造。允许用auto_ptr类型的对象对auto_ptr类型的对象进行隐式构造和显示构造,但是构造之后,资源拥有权会转移给新构造出来的auto_ptr对象,以前的对象不再有资源的拥有权。
(2)不要用裸指针对多个智能指针进行构造,否则会引发堆空间被多次释放,程序崩溃。
为了避免因为同一个裸指针构造多个auto_ptr对象而引发堆空间重复释放程序崩溃的问题,在构造之后,将裸指针置空。
(3)用一个auto_ptr对象给另一个auto_ptr对象进行赋值操作的时候,后者如果本身拥有资源,那么会先将后者本身的资源进行释放,然后前者的资源给后者,再将前者的成员指针变量置空,让前者失去对资源的拥有权。

关于auto_ptr的成员函数:
(1)get()函数返回对象的成员指针变量(即资源的首地址)。
(2)reset()函数将对象的资源释放,将对象的成员指针变量置NULL。
(3)release()函数将对象拥有的资源的首地址返回,将成员指针变量指向NULL(即让对象失去资源的拥有权,返回资源的首地址,并没有释放资源)。

2.auto_ptr部分功能的仿写

#ifndef MAUTO_PTR_H
#define MAUTO_PTR_H

template<typename T>
class Mauto_ptr
{
public:
	explicit Mauto_ptr(const T*& ptr = NULL)//explicit关键字是告诉编译器不允许用裸指针隐式构造
	{
		_ptr = ptr;//新对象获取裸指针的资源(注意没有将裸指针置NULL,所以原指针和新对象共享资源)
	}

	Mauto_ptr(Mauto_ptr& src)//拷贝构造函数
	{
		_ptr = src._ptr;  //新对象获取资源的拥有权
		src._ptr = NULL;//传进来的实参失去对资源的拥有权
	}

	~Mauto_ptr()
	{
		delete _ptr;//如果_ptr == NULL,这句代码也不会出错
	}

	T* get()//返回成员指针变量(该成员指针变量保存资源的首地址)
	{
		return _ptr;
	}
	T* release()//失去资源的拥有权,返回资源的首地址(注意该操作并没有释放资源)
	{
		T* tmp = _ptr;
		_ptr = NULL;//失去对资源的拥有权
		return tmp;  //返回资源的首地址
	}
	void reset()//将拥有的资源释放,成员指针变量置空
	{
		delete _ptr;  //释放拥有的资源
		_ptr = NULL;//成员指针变量置空
	}



	//=、*、->  运算符重载
	Mauto_ptr& operator=(Mauto_ptr& src)//赋值运算符重载
	{
		if (this == &src)//如果自己给自己赋值,那么什么都不做,返回本身
		{
			return *this;
		}
		_ptr = src._ptr;//新对象获取资源的拥有权
		src._ptr = NULL;//传进来的实参失去对资源的拥有权
		return *this;
	}
	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;//成员指针变量,保存资源的首地址,即指向对象拥有的资源
};

#endif

二、unique_ptr的使用

1.unique_ptr的使用和讲解

使用示例及讲解:

unique_ptr<int> fun()//如果有形参,参数必须是引用(指针),否则形参涉及拷贝构造,unique_ptr不允许
{
	return unique_ptr<int>(new int(100));//OK,临时对象
}

int main()
{
	int* p = new int(10);
	//unique_ptr<int> u_ptr1 = p;//不允许隐式构造
	unique_ptr<int> u_ptr1(p);
	cout << "u_ptr1拥有的堆空间的首地址:" << u_ptr1.get() << "\t" << "p指向的堆空间的首地址:" << p << endl;
	//发现u_ptr1和p指向同一个堆空间,所以为了避免重复释放堆空间引起的崩溃问题,一般会执行以下操作:
	//unique_ptr<int>u_ptr1(p);	p = NULL;即让裸指针失去对堆空间的拥有权
	//delete p;//error 这句代码没错,会执行,但是对象u_ptr1的生存期到了之后调用析构函数时,会崩溃,因为有对一个堆空间重复释放的问题
	//不允许用一个裸指针对多个unique_ptr进行赋值或者拷贝构造等和资源有关的操作
	//因为每一个unique_ptr对象生存期到了之后,都会对拥有的资源进行释放,会发生对同一个堆空间重复释放的问题,程序会崩溃
	//所以任何造成“让多个unique_ptr指向同一个资源的”结果的操作,都要避免,否则就会对一个堆空间重复释放,程序会崩溃

	
	//unique_ptr<int>u_ptr2 = u_ptr1;//error,不允许隐式构造
	//unique_ptr<int>u_ptr2(u_ptr1);//error,不允许普通的拷贝构造

	//unique_ptr<int>u_ptr2 = fun();//OK,允许用右值进行隐式拷贝构造
	/*//OK,允许用右值对unique_ptr进行赋值操作
	unique_ptr<int>u_ptr2;//OK
	u_ptr2 = fun();//OK
	*/
	
	unique_ptr<int>u_ptr2(fun());//OK,允许用右值(在这里是即将销毁的临时对象)为实参拷贝构造
	cout << "u_ptr2拥有的堆空间的首地址:" << u_ptr2.get() << "\t" << "u_ptr2的资源的内容为:" << *u_ptr2 << endl;
	//u_ptr2 = u_ptr1;//error,不允许unique_ptr类型的对象之间的赋值操作

	//成员方法:
	//get()会返回对象的成员指针变量(以临时存在的方式返回)
	cout << "u_ptr1拥有的资源的首地址:" << u_ptr1.get() << endl;
	//reset()会将对象拥有的资源释放,成员指针变量置空
	//release()会将对象的成员指针变量置空,返回资源的首地址(并没有释放资源,只是失去对资源的拥有权)
	//int*q =  u_ptr1.release();//OK,注意,在这里是以成员指针变量的类型返回,这里是int*
	unique_ptr<int>u_ptr3(u_ptr1.release());
	cout << "u_ptr1拥有的堆空间的首地址:" << u_ptr1.get() << "\t" << "u_ptr3拥有的堆空间的首地址:" << u_ptr3.get() << endl;
	//operator bool;对bool运算符进行了重载,可以根据以下语句判断u_ptr1是否拥有资源
	if (u_ptr1) { cout << "u_ptr1有资源" << endl; }
	else { cout << "u_ptr1没有资源" << endl; }

	cout << "swap()//" << endl;
	//swap()//将对象拥有的资源交换
	cout << u_ptr2.get() << "    " << u_ptr3.get() << endl;
	u_ptr2.swap(u_ptr3);
	cout << u_ptr2.get() << "    " << u_ptr3.get() << endl;
	
	//=运算符重载
	u_ptr3 = fun();//右值赋值给unique_ptr类型的指针,但是u_ptr3原来的资源并没有被释放
	cout << "u_ptr3拥有的资源的首地址:" << u_ptr3.get() << "\t" << "u_ptr3的资源的内容为:" << *u_ptr3 << endl;
	//注意u_ptr2也是用fun()构造的,但是u_ptr2和u_ptr3的资源不一样,请参考博客右值引用的实质。
	cout << "访问u_ptr3原本的资源内容:" << *p << endl;//注意u_ptr3被fun()赋值之前拥有的资源和p指向的堆空间是同一个
	//上面这句代码输出成功,说明fun()赋值给u_ptr3,u_ptr3并没有把原来拥有的资源释放
	
	
	return 0;
}

在这里插入图片描述
unique_ptr总结:
关于构造和赋值:
(1)对于两个unique_ptr的对象(非右值)来说,unique_ptr杜绝了两个unique_ptr对象之间的操作(包括赋值和拷贝构造)。
(2)对于右值和unique_ptr的对象来说,允许右值和unique_ptr对象之间的操作,包括用右值拷贝构造(隐式也可以),用右值赋值。但是用右值给unique_ptr对象赋值时,如果unique_ptr原本有资源,那么原本的资源不会被释放。
(3)对于裸指针和unique_ptr的对象来说,用裸指针构造unique_ptr对象之后,裸指针仍旧指向资源,所以要注意不要用同一个裸指针构造多个unique_ptr对象,否则每个unique_ptr对象生存期到了之后,都会调用析构对象释放堆空间,会造成一个堆空间被重复释放,导致程序崩溃。所以为了误操作,用裸指针构造了unique_ptr对象之后,最好将裸指针置空。不允许用裸指针隐式构造unique_ptr对象,必须用显示构造。不允许裸指针对unique_ptr进行赋值操作。

关于unique_ptr的成员函数:
(1)get()函数返回对象的成员指针变量(即资源的首地址)
(2)reset()函数将对象的资源释放,将对象的成员指针变量置NULL
(3)release()函数将对象拥有的资源的首地址返回,将成员指针变量指向NULL(即让对象失去资源的拥有权,返回资源的首地址,并没有释放资源)
(4)operator boo;unique_ptr对bool运算符进行了重载,成员指针指向NULL则返回假,代表没资源;成员指针指向资源则为真,代表有资源
(5)unique_ptr释放资源用的是deleter,deleter会自动识别这个资源是应该用delete释放好还是用delete[]释放好。

2.unique_ptr部分功能的仿写

#ifndef MUNIQUE_PTR_H
#define MUNIQUE_PTR_H

template<typename T>
class Munique_ptr
{
public:
	explicit Munique_ptr(T* ptr = NULL)//允许裸指针显示构造
	{
		_ptr = ptr;
	}
	//右值引用--只能用来引用即将死亡的对象
	Munique_ptr(const Munique_ptr&& src)//允许用右值属性的对象拷贝构造
	{
		_ptr = src._ptr;
		src._ptr = NULL;
	}
	Munique_ptr& operator=(Munique_ptr&& src)//允许右值对象进行赋值操作,注意并没有将原资源释放
	{
		if (this == &src)
		{
			return *this;
		}
		_ptr = src._ptr;
		src._ptr = NULL;
		return *this;
	}

	~Munique_ptr()
	{
		delete _ptr;
	}
	T* get()
	{
		return _ptr;
	}
	T* release()//注意只是失去对资源的拥有权,并没有释放资源
	{
		T* tmp = _ptr;
		_ptr = NULL;
		return tmp;
	}
	void reset()
	{
		delete _ptr;
		_ptr = NULL;
	}
	T* operator->()
	{
		return _ptr;
	}
	T& operator*()
	{
		return *_ptr;
	}
	operator bool()
	{
		return _ptr != NULL;
	}
private:
	T* _ptr;
};

#endif

三、shared_ptr的使用

1.shared_ptr的使用和讲解

shared_ptr是共享指针,如果一个共享指针拥有的资源可以同时被其他共享指针拥有,指向同一个资源的共享指针共用同一个引用计数,引用计数为几就意味着这个资源被几个共享指针拥有,当有共享指针失去对资源的拥有权时,或者有共享指针生存期到了时,那么这个资源的引用计数就减1,当引用计数为1,就说明这个资源现在只被一个共享指针拥有。当某个共享指针的生存期到了时,同时资源的引用计数为1时(即这个资源只被这一个共享指针拥有时),那么析构函数里才会执行对资源的释放操作。
(注:所有的这些都注意,前提是不能用同一个裸指针同时构造两个共享指针对象,否则会出错,代码中给出示例)
使用示例及讲解:

int main()
{
	//use.cout()函数可以获取现在的资源被几个shared_ptr对象拥有

	int* p = new int(10);
	//shared_ptr<int>s_ptr1 = p;//error 不允许隐式构造
	shared_ptr<int>s_ptr1(p);//OK,显示构造
	cout << "s_ptr1的资源的首地址:" << s_ptr1.get() << "\t" << "p指向的空间首地址:" << p << endl;
	cout << "s_ptr1拥有的资源的引用计数为:" << s_ptr1.use_count() << endl;

	shared_ptr<int>s_ptr2;
	//s_ptr2 = p;//error不允许用裸指针赋值
	s_ptr2 = s_ptr1;
	cout<< "s_ptr1的资源的首地址:" << s_ptr1.get() << "\t" << "s_ptr2的资源的首地址:" << s_ptr2.get() << endl;
	cout << "s_ptr1拥有的资源的引用计数为:" << s_ptr1.use_count() << endl;

	shared_ptr<int>s_ptr3(s_ptr2);//允许拷贝构造
	cout << "s_ptr3的资源的首地址:" << s_ptr3.get() << endl;
	cout << "s_ptr1拥有的资源的引用计数为:" << s_ptr1.use_count() << endl;

	s_ptr3.reset();//s_ptr3失去对资源的拥有权,由于引用计数大于1,s_ptr3不对资源进行释放
	cout << "s_ptr3成员指针指向为:" << s_ptr3.get() << endl;
	cout << "s_ptr1拥有的资源的引用计数为:" << s_ptr1.use_count() << endl;

	int* q = new int(100);
	shared_ptr<int>s_ptr4(q);
	cout << "s_ptr1的资源的首地址:" << s_ptr1.get() << "\t" << "和s_ptr4拥有同一个资源的对象的个数:" << s_ptr4.use_count() << endl;

	s_ptr4.swap(s_ptr1);
	cout << "s_ptr1的资源的首地址:" << s_ptr1.get() << "\t" << "s_ptr4的资源的首地址:" << s_ptr4.get() << endl;
	cout << "s_ptr1拥有的资源的引用计数为:" << s_ptr1.use_count() << "\t" << "s_ptr4拥有的资源的引用计数为:" << s_ptr4.use_count() << endl;

	/*
	shared_ptr<int>s_ptr5(q);
	cout << "s_ptr5拥有的资源的首地址:" << s_ptr5.get() << "s_ptr5拥有的资源的引用计数:" << s_ptr5.use_count() << endl;
	*/
	//上面被注释掉的代码会是程序崩溃,原因是我们用q指针构造了两个shared_ptr对象,但是这两个对象拥有的资源的引用计数为1
	//s_ptr1(和s_ptr4交换资源后,s_ptr4拥有的是q指向的资源 )和s_ptr4两个指针都指向p
	//但是它们的资源的引用计数都是1
	//所以它们俩生存期到了之后,会对自己的资源进行释放,同一个资源被释放两次,程序崩溃

	return 0;
}

运行结果:
在这里插入图片描述

shared_ptr总结:
关于构造和赋值:
(1)不允许裸指针隐式构造shared_ptr对象;允许裸指针显式构造shared_ptr对象。不会修改裸指针的指向。不允许用一个裸指针构造多个shared_ptr对象,因为会造成同一个资源被多个对象拥有,资源的引用计数却少于这些对象的个数,这样会造成对一个堆空间进行重复释放,程序会崩溃。(我们说的一个资源的引用计数为几就代表被几个共享指针拥有,是排除同一个裸指针构造多个共享指针情况后的)。
(2)允许shared_ptr对象之间的赋值。比如s_ptr1 = s_ptr2;
如果s_ptr1的资源被多个共享指针拥有,那么执行完这句代码后,s_ptr1原来的资源的引用计数减1(因为s_ptr1将不再拥有这个资源),s_ptr2拥有的资源的引用计数加1(因为s_ptr1将也会拥有这个资源)。如果s_ptr1的原资源的引用计数为1,即如果只有s_ptr1一个共享指针拥有,那么原资源将会被释放,然后s_ptr2的资源的引用计数加1。(注意,两个共享指针指向同一个资源的话,那么资源的引用计数是同步对这两个指针而言的)。

关于shared_ptr的成员函数:
(1)reset()使对象失去对资源的拥有权,如果资源的引用计数为1(即只被这一个对象拥有),那么会先释放资源,再将成员指针变量置空。如果资源的引用计数大于1(即被多个对象拥有),那么会先将资源的引用计数减一,再将自己的成员指针变量置空。
(2)注意shared_ptr不含有release()函数
(3)swap()函数会将两个对象的资源交换
(4)unique()函数判断对象拥有的资源引用计数是否为1(即是否只被这一个对象拥有)

2.shared_ptr部分功能的仿写

#ifndef MSHARED_PTR_H
#define MSHARED_PTR_H
#include<map>
using namespace std;

//注意,用裸指针构造对象时,我们并没有将资源引用计数记录到map中,
//当我们用这个对象去构造其他对象了,我们才看map有没有记录,有的话,资源引用计数加一,
//没有的话,这才将资源地址添加到map里记录。
//也就是说,拷贝构造时,如果发现资源没有被添加到map里,那么资源肯定只被实参这一个对象拥有,
//且没有用这个实参拷贝构造果其他对象。
template<typename T>
class Mshared_ptr
{
public:
	explicit Mshared_ptr(T* ptr = NULL)//explicit关键字限制编译器不允许隐式构造
	{
		_ptr = ptr;
	}

	Mshared_ptr(const Mshared_ptr& src)//拷贝构造
	{
		if (_count.end() == _count.find(src._ptr))//说明map里没有记录这个资源
		{
			_count.insert(make_pair(src._ptr, 1));//先将资源添加进去,以引用计数为1添加
		}
		_ptr = src._ptr;
		_count[_ptr]++;
	}
	Mshared_ptr& operator=(const Mshared_ptr& src)
	{
		if (this == &src)//自己对自己赋值,返回原对象
		{
			return *this;
		}
		if (unique())//如果被赋值的对象现在拥有的资源只被它一个拥有,释放资源
		{
			delete _ptr;
		}
		else//说明资源被多个对象拥有,引用计数--就可以了,不用释放资源
		{
			_count[_ptr]--;
		}

		if (_count.end() == _count.find(src._ptr))//说明map里没有将src拥有的资源记录在里面
		{
			_count.insert(make_pair(src._ptr, 1));//用map记录资源引用计数
		}
		_ptr = src._ptr;
		_count[_ptr]++;
		return *this;
	}

	~Mshared_ptr()
	{
		if (unique())//如果资源引用计数为1,释放资源
		{
			delete _ptr;
		}
		else//如果资源被多个共享指针拥有,那么将引用计数减一
		{
			_count[_ptr]--;
		}
		_ptr = NULL;
	}
	T* get()
	{
		return _ptr;
	}
	void reset()
	{
		if (unique())//如果资源只被一个共享指针拥有,释放资源
		{
			delete _ptr;
		}
		else//如果资源被多个共享指针拥有,引用计数减1
		{
			_count[_ptr]--;
		}
		_ptr = NULL;
	}
	bool unique()
	{
		if ((_count.end() == _count.find(_ptr)) || (_count[_ptr] == 1))//如果map里没有记录这个资源(同样代表资源只被一个对象拥有),或者引用计数为1
		{
			return true;
		}
		return false;
	}
	int use_count()
	{
		if (_count.end() == _count.find(_ptr))//如果map里没有记录这个资源,说明资源只被一个对象拥有,返回1
		{
			return 1;
		}
		return _count[_ptr];//否则的话,返回资源的引用计数
	}
	T& operator[](int pos)
	{
		return _ptr[pos];
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	operator bool()
	{
		return _ptr != NULL;
	}
private:
	T* _ptr;
	static map<T*, int> _count;//存放引用计数,注意map不允许键值重复,所以可以用来记录一个资源的引用计数
};

#endif

四、weak_ptr的使用

1.weak_ptr的使用和讲解

看weak_ptr之前先来看一下shared_ptr存在的问题:
代码:

class B;
class A
{
public:
	A() { cout << "A构造函数" << endl; }
	~A() { cout << "A析构函数" << endl; }
	void fun(shared_ptr<B>& p) { a_p = p; }
private:
	shared_ptr<B> a_p;
};

class B 
{
public:
	B() { cout << "B构造函数" << endl; }
	~B() { cout << "B析构函数" << endl; }
	void fun(shared_ptr<A>& p) { b_p = p; }
private:
	shared_ptr<A> b_p;
};

int main()
{
	A* a = new A();
	B* b = new B();
	shared_ptr<A>s_p1(a);
	shared_ptr<B>s_p2(b);
	cout <<"s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
	cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;

	a->fun(s_p2);
	b->fun(s_p1);
	cout << "s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
	cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;

	return 0;
}

运行结果:
在这里插入图片描述

图形解释:
在这里插入图片描述

对象a和对象b为被共享指针拥有的资源,可以看见对象a被s_p1和对象b内的shared_ptr < A >拥有,所以对象a(资源a)的引用计数为2。同理,对象b被s_p2和对象a内的shared_ptr < B >拥有,所以对象b(资源b)的引用计数为2。当shared_ptr< A >s_p1和shared_ptr< B >s_p1的生存期到了之后,由于资源的引用计数都是2,所以资源a和资源b并不会被释放。这个原因就是因为它们内部的共享指针都指向对方。形成了一个环,使对方作为资源的时候,引用计数都加1了。
在这里插入图片描述

当然,可以手动释放对象a或者对象b,但是前面三个指针都谈过,用指向堆空间的指针构造智能指针时,不要再对这个指针进行delete操作了,因为智能指针的诞生就是为了解决对资源自行管理,如果我们再自行delete堆空间,非常容易造成对一个堆空间多次释放,程序崩溃。对于上面的代码是例外,因为sared_ptr解决不了这个环的问题,我们只是测试一下手动delete,平常将资源赋给智能指针之后,一定不要自己手动delete资源。我们在这里尝试自己手动delete掉没有被释放的资源,看一下结果。(后面的weak_ptr的使用才是正常解决方案)
代码1(在主函数末尾添加delete a;):
运行结果:
在这里插入图片描述
注意:程序执行错误,只不过没有崩溃而已。退出码为随机值,这个是错误码,意味着我们操作不对。
A析构函数被调用了两次,也就是a对象被析构两次(这也是为什么返回的是错误码的原因),delete a,会调用a的析构函数,a内部的shared_ptr< B >也会调用析构函数,它指向b,引用计数为1,所以shared_ptr< B >会对b进行delete,所以对象b会被释放,b内部的shared_ptr< A >会调用析构函数,指向对象a,资源a已经自己调用析构函数了,但是资源引用计数在共享指针内部,值为1,所以shared_ptr< A >的内部会进行delete a。所以a调用了两次析构函数。

代码2(在主函数末尾添加delete b;):
运行结果:
在这里插入图片描述
对于代码2,分析和代码一样的。

代码3(在主函数末尾添加delete a; delete b):
运行结果:
在这里插入图片描述
对于代码3,执行delete a时候会造成b被析构两次了,delete b的时候,b已经被析构过了,所以空间里也没有东西,也不会调用析构函数了。
总之,不管同一个对象被析构几次,当对同一个对象进行多次析构的时候,不管是几次析构,只要多于一次,不管程序有没有崩溃,这都是赤裸裸的错误。

这个时候就产生了weak_ptr,这个是专门为了shared_ptr服务的。弱指针只允许用shared_ptr类型的对象拷贝构造(因为weak_ptr的诞生就是为了shared_ptr服务的),弱指针内部有个指针成员变量,指向shared_ptr类型,最终也是指向了shared_ptr的资源,但是这个不会使资源的引用计数增加,换句话说,weak_ptr可以像shared_ptr一样共享资源,也和shared_ptr一样可以获取资源的引用计数,但是weaak_ptr本身不会引起资源的引用计数的变化,只有shared_ptr才会。
使用示例及讲解:

int main()
{
	//弱指针就是为了解决shared_ptr遗留的问题,循环引用问题,所以weak_ptr就是为了shared_ptr服务的
	//弱指针的构造函数的形参是shared_ptr<T&>类型,所以弱指针拷贝构造必须用shared_ptr对象
	int* p = new int(10);
	shared_ptr<int>s_ptr1(p);
	cout << "s_ptr1拥有的资源的引用计数:" << s_ptr1.use_count() << endl;
	//weak_ptr<int> w_ptr1 = s_ptr1;//OK,隐式拷贝构造ye可以
	weak_ptr<int> w_ptr1(s_ptr1);
	cout << "s_ptr1拥有的资源的引用计数:" << s_ptr1.use_count() << endl;
	cout << "w_ptr1拥有的资源的引用计数:" << w_ptr1.use_count() << endl;

	weak_ptr<int>w_ptr2(s_ptr1);
	cout << "s_ptr1拥有的资源的引用计数:" << s_ptr1.use_count() << endl;
	cout << "w_ptr2拥有的资源的引用计数:" << w_ptr2.use_count() << endl;

	int* q = new int(100);
	shared_ptr<int>s_ptr2;
	weak_ptr<int>w_ptr3(w_ptr1);
	cout << "w_ptr1拥有的资源的引用计数:" << w_ptr1.use_count() << endl;
	cout << "w_ptr3拥有的资源的引用计数:" << w_ptr3.use_count() << endl;

	s_ptr2 = s_ptr1;
	cout << "s_ptr1拥有的资源的引用计数:" << s_ptr1.use_count() << endl;
	cout << "s_ptr2拥有的资源的引用计数:" << w_ptr1.use_count() << endl;
	cout << "w_ptr1拥有的资源的引用计数:" << w_ptr1.use_count() << endl;
	cout << "w_ptr2拥有的资源的引用计数:" << w_ptr2.use_count() << endl;
	cout << "w_ptr3拥有的资源的引用计数:" << w_ptr3.use_count() << endl;

	shared_ptr<int>s_ptr3 = w_ptr1.lock();//将弱智能指针转换为强智能指针
	cout << "s_ptr1拥有的资源的引用计数:" << s_ptr1.use_count() << endl;
	cout << "s_ptr2拥有的资源的引用计数:" << w_ptr1.use_count() << endl;
	cout << "w_ptr1拥有的资源的引用计数:" << w_ptr1.use_count() << endl;
	cout << "w_ptr2拥有的资源的引用计数:" << w_ptr2.use_count() << endl;
	cout << "w_ptr3拥有的资源的引用计数:" << w_ptr3.use_count() << endl;

	return 0;
}

在这里插入图片描述

weak_ptr总结:
构造和赋值:
(1)为了解决shared_ptr解决不了的循环指向的问题,weak_ptr协助shared_ptr解决。即weak_ptr可以和shared_ptr之间通过强弱指针转换,拥有资源,而weak_ptr不会使资源的引用计数变化。
(2)weak_ptr是为了服务shared_ptr而诞生的,只允许用shared_ptr对其进行拷贝构造。

成员函数:
(1)reset()
(2)赋值运算符重载,允许用shared_ptr和weak_ptr赋值给weak_ptr,不会引起资源的引用计数的变化。
(3)lock()将弱指针转化为强指针(shared_ptr),被强指针接受,资源引用计数加加。前提是该弱指针内部成员指针变量指向的强指针还存在。
用weak_ptr解决shared_ptr的循环指向问题,上面问题由于对象a和对象b的引用计数都为2,导致没办法析构,让其中一个为1就好了,这样这个对象就会被释放,内部指针指向的另一个对象由于资源引用计数为1,另一个对象也会被释放。将A类或者B类中的一个shared_ptr修改为弱指针就好了。
代码1(将A类中的设置为弱指针):

class B;
class A
{
public:
	A() { cout << "A构造函数" << endl; }
	~A() { cout << "A析构函数" << endl; }
	void fun(shared_ptr<B>& p) { a_p = p; }
private:
	weak_ptr<B> a_p;//注意这里是弱指针
};

class B 
{
public:
	B() { cout << "B构造函数" << endl; }
	~B() { cout << "B析构函数" << endl; }
	void fun(shared_ptr<A>& p) { b_p = p; }
private:
	shared_ptr<A> b_p;
};

int main()
{
	A* a = new A();
	B* b = new B();
	shared_ptr<A>s_p1(a);
	shared_ptr<B>s_p2(b);
	cout <<"s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
	cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;

	a->fun(s_p2);
	b->fun(s_p1);
	cout << "s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
	cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;
	

	return 0;
}

在这里插入图片描述
图形分析:
在这里插入图片描述
注意weak_ptr< B >a_ptr指向shared_ptr< B >s_p2,这是因为weak_ptr内部是一个指针变量,指向的是shared_ptr类型的对象那个(在最后的仿写代码中可以看出来)。
s_p1和s_p2中,s_p2后构造的,先析构s_p2(栈中的对象,先构造的后析构,后构造的先析构),s_p2拥有资源b,资源b的引用计数为1,所以s_p2析构函数中对资源b进行释放,即delete b,b会调用析构函数析构,b对象里的b_ptr也会析构,b_ptr调用析构函数,由于b_ptr拥有资源a,资源a的引用计数为2,所以b_ptr析构函数里只是将资源a的引用计数减减,变为1;s_p1析构,拥有资源a,资源a引用计数为1,所以s_p1的析构函数中会对资源a进行释放,即delete a,所以会执行a的析构函数析构a,对象a中的a_ptr是弱指针,调用默认析构函数析构。
同理,如果是把B类中的shared_ptr指针设为弱指针的话,那么同样分析,就会调用a的析构函数,然后调用b的析构函数。下面将代码和运行结果和图形分析给出:
代码2(将类B中的shared_ptr设置为weak_ptr):

class B;
class A
{
public:
	A() { cout << "A构造函数" << endl; }
	~A() { cout << "A析构函数" << endl; }
	void fun(shared_ptr<B>& p) { a_p = p; }
private:
	shared_ptr<B> a_p;
};

class B 
{
public:
	B() { cout << "B构造函数" << endl; }
	~B() { cout << "B析构函数" << endl; }
	void fun(shared_ptr<A>& p) { b_p = p; }
private:
	weak_ptr<A> b_p;//注意这里是弱指针
};

int main()
{
	A* a = new A();
	B* b = new B();
	shared_ptr<A>s_p1(a);
	shared_ptr<B>s_p2(b);
	cout <<"s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
	cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;

	a->fun(s_p2);
	b->fun(s_p1);
	cout << "s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
	cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;
	

	return 0;
}

运行结果(注意这里就变成先析构对象a了):
在这里插入图片描述
图形分析:
在这里插入图片描述
和代码1分析一样,s_p1和s_p2中,s_p2后构造的,先析构s_p2(栈中的对象,先构造的后析构,后构造的先析构),s_p2拥有资源b,引用计数为2,所以资源b引用计数减减,变为1。s_p1析构,拥有资源a,资源a引用计数为1,所以s_p1析构函数中对资源a进行释放,即delete a,a会调用析构函数析构,a对象里的a_ptr也会析构,由于a_ptr指向资源b,资源b引用计数为1,所以a_ptr析构函数中会对资源进行释放,即delete b,会调用b的析构函数,对象b被析构,对象b内的弱指针b_ptr会调用默认析构函数析构。
代码3(将类A和类B中的shared_ptr都设置为weak_ptr):

class B;
class A
{
public:
	A() { cout << "A构造函数" << endl; }
	~A() { cout << "A析构函数" << endl; }
	void fun(shared_ptr<B>& p) { a_p = p; }
private:
	weak_ptr<B> a_p;//弱指针
};

class B 
{
public:
	B() { cout << "B构造函数" << endl; }
	~B() { cout << "B析构函数" << endl; }
	void fun(shared_ptr<A>& p) { b_p = p; }
private:
	weak_ptr<A> b_p;//弱指针
};

int main()
{
	A* a = new A();
	B* b = new B();
	shared_ptr<A>s_p1(a);
	shared_ptr<B>s_p2(b);
	cout <<"s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
	cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;

	a->fun(s_p2);
	b->fun(s_p1);
	cout << "s_p1拥有的资源的引用计数:" << s_p1.use_count() << endl;
	cout << "s_p2拥有的资源的引用计数:" << s_p2.use_count() << endl;
	

	return 0;
}

运行结果:
在这里插入图片描述
图形分析:
在这里插入图片描述
一样分析,这个比上面两种分析更简单,s_p2后构造的,先析构s_p2,s_p2拥有资源b,资源b引用计数为1,所以在s_p2析构函数中会对资源进行释放,即delete b,所以会执行b的析构函数,所以对象b中的b_ptr也会析构,调用默认析构函数析构。s_p1再析构,s_p1拥有资源a,资源a引用计数为1,所以在s_p1析构函数中会对资源进行释放,即delete a,所以会执行a的析构函数,所以对象a中的a_ptr也会析构,调用默认析构函数析构。

注意:weak_ptr对象不会对资源有任何操作,也不会让资源引用计数有变化,内部是一个成员指针变量指向shared_ptr对象。weak_ptr是用来解决shared_ptr产生的环形指向问题。
注意:lock()成员函数可以将弱指针转化为强指针,即用shared_ptr接收返回值,资源引用计数加加。只有将弱指针用lock()方法转换为强指针,才能进行对资源的操作。

2.weak_ptr的部分功能仿写

#ifndef MWEAK_PTR_H
#define MWEAK_PTR_H
#include"mshared_ptr.h"
template<typename T>
class Mweak_ptr
{
public:
	Mweak_ptr(Mshared_ptr<T>& s_p = NULL)
	{
		_s_p = &s_p;
	}

	Mshared_ptr<T> lock()//将弱指针转化为强指针
	{
		if (_s_p->use_count() == 0)//说明指向的对象没有资源,返回默认构造函数产生的对象
		{
			return Mshared_ptr<T>();
		}
		return *_s_p;
	}

	void reset()//只是将weak_ptr对象的_s_p置空,不对资源有任何操作。
	{
		_s_p = NULL;
	}

	int use_count()
	{
		return _s_p->use_count();
	}
private:
	Mshared_ptr<T>* _s_p;
};

#endif
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孟小胖_H

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值