【C++11】智能指针

一、智能指针的引入

  • 存在内存泄漏

我们先来看一段代码:

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	int* p1 = new int;//p1抛异常,直接跳catch,没有问题
	cout << div() << endl; // div抛异常,调到catch,p1无法释放,资源泄露
	delete p1;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

new空间也有可能会抛出异常,对于p1如果抛出异常:没有问题,可以不管,直接到最外面去了。

而如果用户输入的除数为0,那么div函数就会抛出异常,跳到主函数的catch块中执行,但是别忘了,此时Func()中的申请的内存资源还没有释放!

对于这种情况,我们可以进行异常的重新捕获

  • 异常的重新捕获

在func函数中对div函数中的抛出的异常重新捕获,将申请的内存进行释放,然后在将异常重新捕获:

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	int* p1 = new int;//p1抛异常,直接跳catch,没有问题
	try
	{
		cout << div() << endl;
	}
	catch (...)
	{
		delete p1;
		throw;
	}
	delete p1;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

但是如果申请的不是上面的一块空间,而是更多呢,还有p2,p3…?这时候就麻烦了,需要套很多,所以这时候智能指针就登场了,可以解决这个问题。


二、智能指针的使用与原理

  • 智能指针的使用
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		cout << "delete: " << _ptr << endl;
		delete _ptr;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	T& operator[](size_t pos)
	{
		return _ptr[pos];
	}
private:
	T* _ptr;
};
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{
	SmartPtr<int> sp(new int);
	int* p2 = new int;
	SmartPtr<int> sp2(p2);

	cout << div() << endl;
}
int main()
{
	try
	{
		func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

上面代码将申请到的内存交给了SmartPtr对象管理:

在构造SmartPtr对象时,自动调用构造函数,将传入的需要管理的内存保存起来;

在析构SmartPtr对象时,自动调用析构函数,将管理的内存空间进行释放

SmartPtr还可以与普通指针一样使用,需对*和->以及[]进行运算符重载

通过SmartPtr对象,无论程序是正常执行结束,还是因为某些中途原因进行返回,或者抛出异常等开始所面临的困境,只要SmartPtr对象的生命周期结束就会自动调用对应的析构函数,不会造成内存泄漏,完成资源释放。

  • 智能指针的原理

RAII: RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、互斥量等等)的简单技术。

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保存有效,最后在对象析构时释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象,好处:

不需要显式地释放资源

采用这种方式,对象所需的资源在其生命周期内始终保持有效

  • 智能指针对象拷贝问题

对于我们上面所实现的SmartPtr,如果用一个SmartPtr对象来拷贝构造另一个SmartPtr对象,或者一个SmartPtr对象赋值给另一个SmartPtr对象,最终结果会导致程序崩溃:

void test()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(sp1);//拷贝构造
	SmartPtr<int> sp3(new int);
	SmartPtr<int> sp4 = sp3;//赋值
}

编译器默认生成的拷贝构造函数对内置类型完成浅拷贝(值拷贝),sp1拷贝给sp2后,两个管理同一块空间,当sp1和sp2析构就会让这块内存空间释放两次。同理,编译器默认生成的卡搜被赋值函数对内置类型完成浅拷贝,把sp4赋值给sp3后,sp4与sp3都是管理原来sp3的空间,会析构两次,同时,原先sp4管理的内存没有释放。

单纯的浅拷贝会导致空间多次释放,因为根据智能指针解决卡搜被问题方式不同,所以有很多版本的智能指针


三、C++中的智能指针

auto_ptr

  • auto_ptr的使用

auto_ptr让管理权限进行了转移,这是一个比较坑的地方,我们需要多加注意

auto_ptr是C++98的,通过管理权转移的方式解决智能指针拷贝问题,保证了一个资源只有一个对象对其进行管理,这时候一个资源就不会被多个释放:

int main()
{
	std::auto_ptr<int> ap1(new int(1));
	std::auto_ptr<int> ap2(ap1);
	*ap2 = 10;
	//*ap1 = 10;错误的写法
	std::auto_ptr<int> ap3(new int(1));
	std::auto_ptr<int> ap4(new int(2));
	ap3 = ap4;
	return 0;
}

资源的管理权转移意味该对象不能在对原来管理的资源进行访问了,如果进行访问,会导致程序崩溃,很容易出现问题,所以比较少用。

  • auto_ptr的模拟实现

构造对象获取资源,析构对象释放资源。对*和->运算符进行重载,使其像指针一样。拷贝构造函数,用传入的对象的资源来构造当前对象,并将传入对象管理资源指针悬空。

	template<class T>
	class auto_ptr
	{
	public:
		// RAII
		// 保存资源
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		// 释放资源
		~auto_ptr()
		{
			//delete[] _ptr;
			delete _ptr;
			cout << _ptr << endl;
		}

		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			sp._ptr = nullptr;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
	};

unique_ptr

头文件是《memory》

  • unique_ptr的使用

unique_ptr是C++11中的智能指针,unique_ptr来的更直接:直接防止拷贝的方式解决智能指针的拷贝问题,简单而又粗暴,防止智能指针对象拷贝,保证资源不会被多次释放,但是防止拷贝也不是解决问题的好办法:

std::unique_ptr<int> up1(new int(0));
std::unique_ptr<int> up2(up1);

image-20230401223025834

  • unique_ptr的模拟实现

构造函数中获取资源,在析构函数中释放资源。对*->运算符进行重载,使unique_ptr对象具有指针一样的行为。

C++98的方式是将拷贝构造函数和拷贝赋值函数声明为私有;C++11的方式就直接在这两个函数后面加上=delete,防止外部进行调用:

	template<class T>
	class unique_ptr
	{
	public:
		// RAII
		// 保存资源
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		// 释放资源
		~unique_ptr()
		{
			//delete[] _ptr;
			delete _ptr;
			cout << _ptr << endl;
		}

		unique_ptr(const unique_ptr<T>& up) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
	};

shared_ptr

  • shared_ptr的使用

shared_ptr是C++11的智能指针,通过引用计数的方式解决智能指针的拷贝问题。

每个被管理的资源有有一个对应的引用计数,这个引用计数记录当前有多少对象在管理这块资源。

每新增加一个对象管理这块资源则对该资源的引用计数++;当一个对象不在管理这块资源或对象析构时那么该资源对应的引用计数–

当一个资源的引用计数为0时那么就说明已经没有对象在管理这块资源了,这时候就可以进行释放了。

引用计数的方式能够支持多个对象一起管理一个资源,也就支持智能指针的拷贝,只有当资源的引用计数减为0时才会释放,保证了同一个资源不会被多次释放:

int main()
{
	std::shared_ptr<int> sp1(new int(1));
	std::shared_ptr<int> sp2(sp1);
	*sp1 = 10;
	*sp2 = 20;
	cout << sp1.use_count() << endl; //2
    //use_count:用于获取当前对象管理的资源对应的引用计数。
	std::shared_ptr<int> sp3(new int(1));
	std::shared_ptr<int> sp4(new int(2));
	sp3 = sp4;
	cout << sp3.use_count() << endl; //2
	return 0;
}

image-20230401224143476

  • shared_ptr的模拟实现

shared_ptr每增加一个成员变量pcount,表示只能指针对象管理的资源对应的引用计数。

构造函数获取资源时,同时将对应于的引用计数设为1,表示当前一个对象在管理这块资源;析构函数,将管理资源对应的引用计数–,如果为0就需要进行释放

拷贝构造函数中,与传入对象一起管理资源,将该资源的引用计数++

对于拷贝赋值:先将当前对象管理的资源对应的引用计数–,为0时需要释放,然后在传入对象一起管理资源。将该资源对应的引用计数++

对*和->进行运算符重载,具有像指针一样的行为。

	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		// 保存资源
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _pcount(new int(1))
		{}

		// 释放资源
		~shared_ptr()
		{
			Release();
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}

		void Release()
		{
			if (--(*_pcount) == 0)
			{
				delete _pcount;
				delete _ptr;
			}
		}
        
        //sp1 = sp1;
        //sp1 = sp2;//sp2如果是sp1的拷贝呢?
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)//资源地址不一样
			{
				Release();
				_pcount = sp._pcount;
				_ptr = sp._ptr;
				++(*_pcount);
			}

			return *this;
		}
        
        int use_count()
		{
			return *_pcount;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
		int* _pcount;
	};

小细节:引用计数需要存放在堆区

对于int:shared_ptr的引用计数可不能直接定义成int类型的成员变量,如果是int类型那么每个shared_ptr对象都有自己的pcount成员变量,而当多个对象管理同一块资源时,这几个对象应该是用同一个引用计数!

对于静态:shared_ptr的引用计数不能定义成静态的成员变量,如果是静态成员变量,那么是所有类型对象共享的,这会导致管理相同资源的对象和管理不同资源的对象都是用同一个引用计数!

shared_ptr的引用计数定义成指针,当一个资源第一次被管理时就在堆区开辟一块空间用于存储对应的引用计数,如果其他对象也要管理这块资源,那么除了将这个资源给它,引用计数也要给它。此时管理同一个资源的多个对象访问到的就是同一个引用计数了,管理不同资源的对象访问到的就是不同的引用计数,符合我们的要求。释放问题:引用计数的内存空间在堆上开辟,当资源的引用计数减到0时,要把该资源释放,同时也要把该资源对应的引用计数的内存空间释放。

shared_ptr线程安全问题

我们上面模拟实现的shared_ptr其实还存在着线程安全的问题,管理同一个资源的多个对象共享引用计数,多个线程可能会同时对同一个个引用计数进行加或减,而自增或自减都不是原子操作,所以需要通过加锁对引用计数进行保护。

比如下面代码中用一个shared_ptr管理一个整型变量,然后用两个线程,通过Lambda表达式分别对这个shared_ptr对象进行1000次拷贝操作,这些对象被拷贝出来后又会立即被销毁:

void test_shared_ptr()
	{
		int n = 1000;
		shared_ptr<int> sp1(new int(1));

		thread t1([&]()
		{
			for (int i = 0; i < n; i++)
			{
				hwc::shared_ptr<int> sp2(sp1);
			}
		});

		thread t2([&]()
		{
			for (int i = 0; i < n; i++)
			{
				hwc::shared_ptr<int> sp3(sp1);
			}
		});

		t1.join();
		t2.join();
    	cout << sp1.use_count() << endl;
	}

在这个过程中两个线程会不断对引用计数进行自增和自减操作,理论上最终两个线程执行完毕后引用计数的值应该是1

(因为拷贝出来的对象都被销毁了,只剩下最初的shared_ptr对象还在管理这个整型变量)但是实际上:

每次运行程序得到引用计数的值可能都是不一样的,根本原因就是因为对引用计数的自增和自减不是原子操作

  • 加锁解决shared_ptr线程安全问题

通过加锁让引用计数的++、–操作变成原子操作,对引用计数的操作进行加锁保护,也可以用原子类atomic对引用计数封装。这里只使用加锁,shared_ptr加锁版本:

在shared_ptr类中新增加互斥锁成员变量,让管理同一个资源的多个线程访问到的是同一个互斥锁,管理不同资源的线程访问到的就是不同的互斥锁,所以互斥锁也在堆区创建,调用拷贝构造和拷贝赋值时,应当将对应的资源交给对方对象管理,同时还要将对应的互斥锁交给当前对象。同理,当资源的引用计数减为0时,除了需将对应的资源和引用计数进行释放,由于互斥锁在堆区创建,所以也需要将互斥锁进行释放:

template<class T>
	class shared_ptr
	{
	public:
		// RAII
		// 保存资源
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _pcount(new int(1))
			,_pmtx(new mutex)
		{}

		// 释放资源
		~shared_ptr()
		{
			Release();
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			,_pmtx(sp._pmtx)
		{
			_pmtx->lock();//t1,t2
			++(*_pcount);
			_pmtx->unlock();
		}

		void Release()
		{
			bool flag = false;
			_pmtx->lock();
			if (--(*_pcount) == 0)
			{
				delete _pcount;
				delete _ptr;
				flag = true;
			}
			_pmtx->unlock();
			if (flag == true) delete _pmtx;
		}

		//sp1 = sp1;
		//sp1 = sp2;//sp2如果是sp1的拷贝呢?
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)//资源地址不一样
			{
				Release();
				_pcount = sp._pcount;
				_ptr = sp._ptr;
				_pmtx = sp->_pmtx;

				_pmtx->lock();
				++(*_pcount);
				_pmtx->unlock();
			}

			return *this;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}

		int use_count()
		{
			return *_pcount;
		}
	private:
		T* _ptr;
		int* _pcount;
		mutex* _pmtx;
	};

flag作用:当引用计数减到0时需要释放互斥锁,但是不能在临界区直接进行释放,因为后面还要解锁,所以可以通过flag去标记,判断解锁后是否释放互斥锁资源。

shared_ptr本身是线程安全的(拷贝和析构时,引用计数++,--是线程安全的),不需要保证管理的资源的线程安全问题

shared_ptr管理资源的访问不是线程安全的,需要用的地方自行保护

举个例子:比如管理日期类资源:

struct Date
	{
		int _year = 0;
		int _month = 0;
		int _day = 0;
	};

	void test_shared_ptr()
	{
		int n = 100000;
		shared_ptr<Date> sp1(new Date);

		thread t1([&]()
		{
			for (int i = 0; i < n; i++)
			{
				shared_ptr<Date> sp2(sp1);
				sp2->_year++;
				sp2->_month++;
				sp2->_day++;
			}
		});

		thread t2([&]()
		{
			for (int i = 0; i < n; i++)
			{
				shared_ptr<Date> sp3(sp1);
				sp3->_year++;
				sp3->_month++;
				sp3->_day++;
			}
		});

		t1.join();
		t2.join();
		cout << sp1.use_count() << endl;

		cout << sp1->_year << endl;
		cout << sp1->_month << endl;
		cout << sp1->_day << endl;
	}

每次运行结果值都不一样。要想保证安全,可以自己定义一个锁:

image-20230402000745970

shared_ptr定制删除器

  • 定制删除器的使用

智能指针对象的生命周期结束时,默认是以delete的方式将资源释放,这是不太合适的,因为智能指针并不是只管理以new方式申请到的内存空间,也可能是以new[]的方式申请到的空间,或是一个文件指针:

struct ListNode
{
	ListNode* _next;
	ListNode* _prev;
	int _val;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
int main()
{
	std::shared_ptr<ListNode> sp1(new ListNode[10]); 
	std::shared_ptr<FILE> sp2(fopen("test.cpp", "r"));
	return 0;
}

nwe[]申请内存空间必须以delete[]方式进行释放,文件指针要fclost进行释放。这时通过定制删除器来控制资源的释放:

image-20230402083718412

p:需要让智能指针管理的资源。
del:删除器,这个删除器是一个可调用对象,比如函数指针、仿函数、lambda表达式以及被包装器包装后的可调用对象。
当shared_ptr对象的生命周期结束时就会调用传入的删除器完成资源的释放,调用该删除器时会将shared_ptr管理的资源作为参数进行传入。

因此当智能指针管理的资源不是以new的方式申请到的内存空间时,就需要在构造智能指针对象时传入定制的删除器。比如:

//定制删除器
template <class T>
struct DeleteArray
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
		cout << "delete []" << ptr << endl;
	}
};
int main()
{
	std::shared_ptr<int> sp1(new int[10],DeleteArray<int>());
	std::shared_ptr<string> sp2(new string[10],DeleteArray<string>());
    
	return 0;
}
  • 定制删除器的模拟实现

我们是直接用一个类来模拟实现shared_ptr的,不能将删除器的类型设置为构造函数的参数模板,因为删除器不是在构造函数中调用,而是在Release中进行调用,所以需要多加一个成员变量把删除器保存下来,定义这个成员变量需要指定删除器的类型,所以模拟实现的时候不能将删除器的类型设置为构造函数的模板参数。

给shared_ptr增加一个模板参数,构造shared_ptr对象需要指定删除器的类型。增加一个支持删除器的构造函数,在构造对象把删除器保存下来,可以设置一个默认的删除器,如果用户定义shared_ptr对象没有传入,默认以delete的方式进行释放:

template <class T>
	class deault_delete
	{
	public:
		void operator()(T* ptr)
		{
			delete ptr;
		}
	};

	template<class T,class D  =deault_delete<T>>
	class shared_ptr
	{
	public:
		// RAII
		// 保存资源
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
			,_pmtx(new mutex)
			
		{}


		// 释放资源
		~shared_ptr()
		{
			Release();
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			,_pmtx(sp._pmtx)
		{
			_pmtx->lock();//t1,t2
			++(*_pcount);
			_pmtx->unlock();
		}

		void Release()
		{
			bool flag = false;
			_pmtx->lock();
			if (--(*_pcount) == 0)
			{
				delete _pcount;
				//delete _ptr;
				_del(_ptr);
				flag = true;
			}
			_pmtx->unlock();
			if (flag == true) delete _pmtx;
		}

		//sp1 = sp1;
		//sp1 = sp2;//sp2如果是sp1的拷贝呢?
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)//资源地址不一样
			{
				Release();
				_pcount = sp._pcount;
				_ptr = sp._ptr;
				_pmtx = sp->_pmtx;

				_pmtx->lock();
				++(*_pcount);
				_pmtx->unlock();
			}

			return *this;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}

		int use_count() const
		{
			return *_pcount;
		}

		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pcount;
		mutex* _pmtx;

		D _del;
	};

如果传入的删除器是仿函数,那么在构造shared_ptr对象要指明仿函数的类型。如果传入的删除器是Lambda表达式,那么就很麻烦了,Lambda表达式类型难以获取,毕竟我们模拟实现的shared_ptr肯定是使用起来没有C++标准库中的那么方便:

//定制删除器
template <class T>
struct DeleteArray
{
	void operator()(const T* ptr=nullptr)
	{
		delete[] ptr;
		cout << "delete []" << ptr << endl;
	}
};
int main()
{
	hwc::shared_ptr<ListNode> n1(new ListNode);
	hwc::shared_ptr<ListNode, DeleteArray<ListNode>> sp1(new ListNode[10]);
	return 0;
}

shared_ptr循环引用问题

shared_ptr的循环引用问题在一些特定的场景下才会产生。比如定义如下的结点类,并在结点类的析构函数中打印一句提示语句,便于判断结点是否正确释放;我们之前的玩法,n1和n2链表进行链接,并进行释放,会调用析构:

struct ListNode
	{
		ListNode* _next;
		ListNode* _prev;

		~ListNode()
		{
			cout << "~ListNode()" << endl;
		}
	};

	void test_shared_ptr2()
	{
		ListNode* n1 = new ListNode;
		ListNode* n2 = new ListNode;

		n1->_next = n2;
		n2->_prev = n1;

		delete n1;
		delete n2;
	}

image-20230327160104062

上述程序是没有问题的,两个结点都能够正确释放。为了防止程序中途返回或抛异常等原因导致结点未被释放,我们将这两个结点分别交给两个shared_ptr对象进行管理,现在套上智能指针shared_ptr:

	struct ListNode
	{
		shared_ptr<ListNode> _next;
		shared_ptr<ListNode> _prev;

		~ListNode()
		{
			cout << "~ListNode()" << endl;
		}
	};
	void test_shared_ptr2()
	{
		std::shared_ptr<ListNode> n1 (new ListNode);
		std::shared_ptr<ListNode> n2 (new ListNode);

		n1->_next = n2;
		n2->_prev = n1;
    }

结果并没有调用析构函数。但如果去掉连接结点时的两句代码中的任意一句,那么这两个结点就都能够正确释放.

这是因为这两句连接结点的代码导致了循环引用,导致了内存泄漏:

image-20230327163709324

循环引用导致资源未被释放的原因

当资源对应的引用计数减为0时对应的资源才会被释放,因此资源1的释放取决于资源2当中的prev成员,而资源2的释放取决于资源1当中的next成员。而资源1当中的next成员的释放又取决于资源1,资源2当中的prev成员的释放又取决于资源2,于是这就变成了一个死循环,最终导致资源无法释放。
而如果连接结点时只进行一个连接操作,那么当node1和node2的生命周期结束时,就会有一个资源对应的引用计数被减为0,此时这个资源就会被释放,这个释放后另一个资源的引用计数也会被减为0,最终两个资源就都被释放了,这就是为什么只进行一个连接操作时这两个结点就都能够正确释放的原因。

weak_ptr

  • weak_ptr的使用

weak_ptr是C++11中引入的智能指针,weak_ptr不是用来管理资源的释放的,它主要是用来解决shared_ptr的循环引用问题的。

weak_ptr支持用shared_ptr对象来构造weak_ptr对象,构造出来的weak_ptr对象与shared_ptr对象管理同一个资源,但不会增加这块资源对应的引用计数,解决上述问题:

    struct ListNode
	{
		std::weak_ptr<ListNode> _prev;
		std::weak_ptr<ListNode> _next;

		~ListNode()
		{
			cout << "~ListNode()" << endl;
		}
	};

	void test_shared_ptr2()
	{
		std::shared_ptr<ListNode> n1 (new ListNode);
		std::shared_ptr<ListNode> n2 (new ListNode);

		n1->_next = n2;
		n2->_prev = n1;

		cout << n1.use_count() << endl;
		cout << n2.use_count() << endl;
	}

image-20230327165705734

通过use_count获取这两个资源对应的引用计数:结点连接前后这两个资源对应的引用计数就是1,原因是weak_ptr不参与资源管理,不会增加管理的资源对应的引用计数。

  • weak_ptr的模拟实现

无参的构造函数;支持用shared_ptr拷贝构造weak_ptr对象,构造时获取shared_ptr对象管理的资源;支持shared_ptr对象拷贝赋值给weak_ptr对象,赋值时获取shared_ptr对象管理的资源,对*和->运算符进行重载,让weak_ptr对象像指针一样具有指针的行为

expired:是否过期

template <class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{

		}
		weak_ptr(const shared_ptr<T>&sp)
			:_ptr(sp.get())
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}
        T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
        T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	public:
		T* _ptr;
	};

shared_ptr提供的get函数:用于获取其管理的资源


四、C++11与boost中智能指针的关系

  • C++11和boost中智能指针的关系

C++98中产生了第一个智能指针auto_ptr。
C++boost给出了更实用的scoped_ptr、shared_ptr和weak_ptr。
C++TR1,引入了boost中的shared_ptr等。不过注意的是TR1并不是标准版。
C++11,引入了boost中的unique_ptr、shared_ptr和weak_ptr。需要注意的是,unique_ptr对应的就是boost中的scoped_ptr,并且这些智能指针的实现原理是参考boost中实现的。

猜你喜欢

转载自blog.csdn.net/weixin_60478154/article/details/129907097