25.智能指针(auto_ptr的使用和模拟实现、shared_ptr的使用和模拟实现、unique_ptr的使用和模拟实现、weak_ptr的使用和模拟实现、智能指针的线程安全问题)

1. 为什么需要智能指针?

下面我们先分析一下下面这段程序有没有什么内存方面的问题?

  • 场景一
// 如果p1和p2 new不会抛异常,仅仅只考虑div()会出现异常
int div()
{
	int a, b;
	cin >> a >> b;
    // 如果函数div()出现除0错误,则会抛异常
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
    // 1、如果p1这里new 抛异常会如何?
    // 如果是p1抛异常,那么就会直接跳转到main()函数对应的catch()中,此时并没有需要去释放的资源,因此是不需要处理的
	int* p1 = new int;
    
    // 2、如果p2这里new 抛异常会如何?
    // 此时p1的资源已经被申请了,如果说,此时p2抛异常,那么就会直接跳转到main()函数对应的catch()中,然而p1申请的资源并没有被释放,这样就会造成内存泄漏
	int* p2 = new int;
    // 如果div()会抛异常,那么throw后会直接跳转到main()函数,这样的话p1和p2就不会被释放,那样就会造成内存泄漏
	cout << div() << endl;
	delete p1;
	delete p2;
}

int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
    
	return 0;
}
  • 场景二
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void func()
{
    // p1抛异常,没有要释放的资源,所以不会造成内存泄漏
	int* p1 = new int[10];
    // p2初始化
	int* p2 = nullptr;
	try
	{
        // p2抛异常,因为p2被try块保护,所以跳转到下面的catch块里面,会先释放p1的资源,再处理异常
        // 因此不会造成内存泄漏,div() 同理
        // 这样的处理方式是正确的,但是代码会十分冗余
        // 如果还有p3、p4等等也要抛异常呢,这样就会变得十分冗余和复杂了
        // 因此才有了智能指针的提出,来解决这个问题
		p2 = new int[20];

		try
		{
			cout << div() << endl;
		}
		catch (...)
		{
			delete[] p1;
			delete[] p2;

			throw;
		}
	}
	catch (...)
	{
        delete[] p1;
		//...
	}

	delete[] p1;
	delete[] p2;
}

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

	return 0;
}

2.智能指针

2.1 RAII

  • **RAII(Resource Acquisition Is Initialization)**是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
  • ​ **在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。**借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
    • 不需要显式地释放资源。
    • 采用这种方式,对象所需的资源在其生命期内始终保持
// 智能指针的类模板
template<class T>
class SmartPtr
{
public:
    // 构造函数
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

    // 析构函数
	~SmartPtr()
	{
		delete[] _ptr;
        cout << _ptr << endl;
	}
private:
	T* _ptr;
};

// 除法函数
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}
  • 使用智能指针

1、如果p1这里new 抛异常会如何?
2、如果p2这里new 抛异常会如何?
3、如果div()调用这里又会抛异常会如何?

void Func()
{
    // 如果是p1抛异常,那么就会直接跳转到main()函数对应的catch()中,此时并没有需要去释放的资源,因此是不需要处理的
	int* p1 = new int[10];
	SmartPtr<int> sp1(p1);

    // 如果是p2抛异常,那么就会直接跳转到main()函数对应的catch()中,此时p1申请的资源是需要进行释放的,但是此时p1的资源是归智能指针的对象sp1管理的,因次当抛异常跳转到main()之后,栈帧还是是正常销毁,当Func()的栈帧销毁,对象sp1就会调用p1的析构函数,将它的资源进行释放,因此p1申请的资源会被释放,并不会造成内存泄漏
	int* p2 = new int[20];
	SmartPtr<int> sp2(p2);

    // 当div()抛异常时,跳转到main(),Func()的栈帧销毁,sp1和sp2会调用p1和p2的析构函数,释放p1和p2管理的资源,并不会造成内存泄漏。
	cout << div() << endl;
     // 可以自己进行调试查看,以下是我调试的结果,p1和p2都被释放了
    // 1 0
	// 00B85EC0
	// 00B86058
	// 除0错误
}

int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

2.2 像指针一样(Smartptr)

​ 上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:SmartPtr模板类中还得需要将* 和 ->重载下,才可让其像指针一样去使用。

  • 智能指针的类模板
template<class T>
 class SmartPtr
 {
 public:
    // 构造函数和析构函数,被称为RAII
    // 构造函数用来保存资源
	 SmartPtr(T* ptr)
		 :_ptr(ptr)
	 {}

    // 析构函数用来释放资源
	~SmartPtr()
	{
		delete[] _ptr;
		delete _ptr;
		cout << _ptr << endl;
	}

    // *,->,[]的重载被称为像指针一样,也就是像指针一样去使用
	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()
{
    // 此时可以通过智能指针(解引用,或者->)访问new的资源,而不需要通过p1/p2
    SmartPtr<int> sp1(new int[10]); 
    SmartPtr<int> sp2(new int[20]);

    *sp1 = 10;
    sp1[0]--;

    cout << *sp1 << endl;  // 打印结果为9
    cout << div() << endl; 
}

int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

2.3 std::auto_ptr

  • C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。

  • auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份qwy::auto_ptr来了解它的原理。

  • 对于auto_ptr我们仅限于了解,拒绝使用它。

namespace  qwy
{
    // 智能指针的类模板
	template<class T>
	class auto_ptr
	{
	public:
		// RAII
		// 保存资源
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

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

	 // 拷贝构造
     // ap2(ap1);
	 // 将ap1.ptr置空了
     // 此处不可以使用const修饰auto_ptr<T>& sp,否则_ptr(sp._ptr)初始化之后,_ptr的权限也会被缩小
		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
            // 将原始的智能指针置空。因为一份资源,只能够由一个智能指针进行管理
			sp._ptr = nullptr;
		}

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

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

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

int main()
{
	qwy::auto_ptr<int> ap1(new int);
	qwy::auto_ptr<int> ap2(ap1);// 拷贝之后,ap1变为了空指针

	(*ap2)++;
    
    // 根据auto_ptr的底层,此时ap1.ptr已经被置为空指针了,因此此处会报错
	(*ap1)++;

	return 0;
}

2.4 std::unique_ptr

​ 为了防止auto_ptr的问题,unique_ptr应运而生,其原理:简单粗暴 – >防拷贝(防止智能指针对象被拷贝)。

namespace  qwy
{
    // 智能智能指针的类模板
	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 _ptr;
		}

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

int main()
{
    qwy::unique_ptr<int> up1(new int);
   
    // 此处会报错,因为unique_ptr类型的智能指针对象是禁止被拷贝的
	qwy::unique_ptr<int> up2(up1);   
    
    return 0;
}

2.5 std::shared_ptr

  • shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
  • 智能指针的类模板
namespace qwy
{
    // 智能指针的类模板
    template<class T>
	class shared_ptr
	{
	public:
		// RAII
		// 保存资源
        // _pcount(new int(1)),在堆上面开一块空间来存放这个计数
        // 构造函数,将创建的int类型的变量初始化为1,表示现在只有一份资源
        // 将资源的指针初始化给_ptr
		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)
			{
                // sp1 = sp4
                // 将sp4赋值给sp1,那么为了防止赋值后,sp1的原有的资源无人管理,造成内存泄漏,那么首先需要将sp1进行释放,再进行赋值
                // sp1调用的Release(), this->Release()
				Release();
				_pcount = sp._pcount;
				_ptr = sp._ptr;
				++(*_pcount);
			}

            // *this就是赋值后得到的对象
			return *this;
		}

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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
        
	private:
		T* _ptr;
		int* _pcount;   // 计数器
	};
}
  • 使用智能指针
int main()
{
    qwy::shared_ptr<int> sp1(new int(0));
	qwy::shared_ptr<int> sp2(sp1);
	qwy::shared_ptr<int> sp3(sp2);
	(*sp1)++;
	(*sp2)++;

	cout << *sp1 << endl;
	cout << *sp2 << endl;
    // 打印结果为:
	// 2
	// 2
	sp1 = sp2;

	qwy::shared_ptr<int> sp4(new int(10));
	qwy::shared_ptr<int> sp5(sp4);
	sp1 = sp4;

	return 0;
}

3.Shared_Ptr的线程安全问题

SmartPtr.h

#pragma once
#pragma once

#include <thread>
#include <mutex>

// 1、RAII
// 2、像指针一样使用
// 3、拷贝问题
namespace qwy
{
    // 智能指针的类模板
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		// 保存资源
		// _pcount(new int(1)),在堆上面开一块空间来存放这个计数
		// 构造函数,将创建的int类型的变量初始化为1,表示现在只有一份资源
		// 将资源的指针初始化给_ptr
		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;  两侧间接为同一个对象
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				// sp1 = sp4
				// 将sp4赋值给sp1,那么为了防止赋值后,sp1的原有的资源无人管理,造成内存泄漏,那么首先需要将sp1进行释放,再进行赋值
				// sp1调用的Release(), this->Release()
				Release();
				_pcount = sp._pcount;
				_ptr = sp._ptr;
				++(*_pcount);
			}

			// *this就是赋值后得到的对象
			return *this;
		}

		int use_count() const
		{
			return *_pcount;
		}

		T* get() const
		{
			return _ptr;
		}

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

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

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

	private:
		T* _ptr;
		int* _pcount;   // 计数器
	};



	// shared_ptr 本身是线程安全的。(拷贝和析构时,引用计数++ --是线程安全的)
	// shared_ptr 管理资源的访问不是线程安全的,需要用的地方自行保护。
	void test_shared_ptr1()
	{
		int n = 10000;
		qwy::shared_ptr<int> sp1(new int(1));

		// 使用lambda表达式来构造这个线程
		thread t1([&]()
			{
				for (int i = 0; i < n; ++i)
				{
					qwy::shared_ptr<int> sp2(sp1);
				}
			});

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

		t1.join();
		t2.join();

		cout << sp1.use_count() << endl;
		cout << sp1.get() << endl;
	}
	
}

main

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

#include "SmartPtr.h"

int main()
{

	qwy::test_shared_ptr1();

	return 0;
}

test_shared_ptr1()

// 情况1:
void test_shared_ptr1()
{
    int n = 10000;
    qwy::shared_ptr<int> sp1(new int(1));

    // 使用lambda表达式来构造这个线程
    thread t1([&]()
              {
                  for (int i = 0; i < n; ++i)
                  {
                      qwy::shared_ptr<int> sp2(sp1);
                  }
              });

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

    t1.join();
    t2.join();

    cout << sp1.use_count() << endl;
    cout << sp1.get() << endl;
}
  • 打印结果

image-20230815140152638

  • 原因

image-20230815175109078

  • 注:线程1和线程2并行时,同时拿到引用计数,假设此时的引用计数是2,那么两个线程拿到的都是2,两个线程++后都会得到的引用计数都是3,写回到内存引用计数是3。(实际上此时的引用计数应该是4)

加锁(解决引用计数线程安全问题)

  • 用锁对拷贝构造、赋值拷贝、析构中的++或者–进行保护
  • 加锁保护之后,则不会再出现上面的线程安全问题
#pragma once

#include <thread>
#include <mutex>

// 1、RAII
// 2、像指针一样使用
// 3、拷贝问题
namespace qwy
{
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		// 保存资源
		// 构造函数
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
			, _pmtx(new mutex)      // 私有成员变量中,加了一个锁对象
		{}


		// 析构函数
		// 释放资源
		~shared_ptr()
		{
			Release();
		}

		// 拷贝构造
		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			, _pmtx(sp._pmtx)
		{
			// 使用锁对++进行保护
			// 多个线程只能够串联执行
			_pmtx->lock(); 
			++(*_pcount);
			_pmtx->unlock();
		}

		void Release()
		{
			// 每个线程都有自己的独立栈结构,因此每个线程都有自己的flag,
			// 因此当flag为ture时,是没有线程安全问题的,此时将智能指针对象释放
			// 进程的堆空间,全局变量、静态变量是所有线程间共享的
			bool flag = false;

			_pmtx->lock();
			// *_pcount是堆上面的资源
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;

				// 当引用计数*_pcount为0时,此时我们不仅需要将_ptr和_pcount进行释放,
				// 还需要将锁进行释放,且必须是解锁之后,在释放锁,所以当*_pcount为0时
				// 将flag赋值为true,代表我们可以释放锁了
				flag = true;
			}
			_pmtx->unlock();

			if (flag == true)
			{
				delete _pmtx;
			}
		}

		// 赋值拷贝
		// sp1 = sp1;
		// sp1 = sp2;
		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;
		}

		int use_count() const
		{
            // 智能指针管理的资源的引用计数
			return *_pcount;
		}

		T* get() const
		{
			return _ptr;
		}

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

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

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

	// shared_ptr 本身是线程安全的。(拷贝和析构时,引用计数++ --是线程安全的)
	// shared_ptr 管理资源的访问不是线程安全的,需要用的地方自行保护。
	void test_shared_ptr1()
	{
		int n = 10000;
		qwy::shared_ptr<int> sp1(new int(1));

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

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

		t1.join();
		t2.join();

		cout << sp1.use_count() << endl;
		cout << sp1.get() << endl;
	}
	
}

加锁(解决智能指针资源线程安全问题)

#pragma once

#include <thread>
#include <mutex>

// 1、RAII
// 2、像指针一样使用
// 3、拷贝问题
namespace qwy
{
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		// 保存资源
		// 构造函数
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
			, _pmtx(new mutex)
		{}


		// 析构函数
		// 释放资源
		~shared_ptr()
		{
			Release();
		}

		// 拷贝构造
		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			, _pmtx(sp._pmtx)
		{
			// 使用锁对++进行保护
			// 多个线程只能够串联执行
			_pmtx->lock(); 
			++(*_pcount);
			_pmtx->unlock();
		}

		void Release()
		{
			bool flag = false;

			_pmtx->lock();
			// *_pcount是堆上面的资源
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;

				// 当引用计数*_pcount为0时,此时我们不仅需要将_ptr和_pcount进行释放,
				// 还需要将锁进行释放,且必须是解锁之后,在释放锁,所以当*_pcount为0时
				// 将flag赋值为true,代表我们可以释放锁了
				flag = true;
			}
			_pmtx->unlock();

			if (flag == true)
			{
				delete _pmtx;
			}
		}

		// 赋值拷贝
		// sp1 = sp1;
		// sp1 = sp2;
		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;
		}

		int use_count() const
		{
			return *_pcount;
		}

		T* get() const
		{
			return _ptr;
		}

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

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

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

    // 日期类
	struct Date
	{
		int _year = 0;
		int _month = 0;
		int _day = 0;
	};

	// shared_ptr 本身是线程安全的。(拷贝和析构时,引用计数++ --是线程安全的)
	// shared_ptr 管理资源的访问不是线程安全的,需要用的地方自行保护。
	void test_shared_ptr1()
	{
		int n = 50000;
		qwy::shared_ptr<Date> sp1(new Date);

		thread t1([&]()
			{
				for (int i = 0; i < n; ++i)
				{
                    // 此处_year++,_day++,_month++有线程安全的问题
					qwy::shared_ptr<Date> sp2(sp1);
					sp2->_year++;
					sp2->_day++;
					sp2->_month++;
				}
			});

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

		t1.join();
		t2.join();

		cout << sp1.use_count() << endl;
		cout << sp1.get() << endl;

		cout << sp1->_year << endl;
		cout << sp1->_month << endl;
		cout << sp1->_day << endl;
	}
}
  • 打印结果

image-20230815184013108

  • 解决方法(加锁)
// shared_ptr 本身是线程安全的。(拷贝和析构时,引用计数++ --是线程安全的)
// shared_ptr 管理资源的访问不是线程安全的,需要用的地方自行保护。

void test_shared_ptr1()
{
    int n = 50000;
    mutex mtx;
    qwy::shared_ptr<Date> sp1(new Date);

    thread t1([&]()
              {
                  for (int i = 0; i < n; ++i)
                  {
                      // 访问智能指针的资源时,进行加锁,避免线程安全的问题
                      qwy::shared_ptr<Date> sp2(sp1);
                      mtx.lock();
                      sp2->_year++;
                      sp2->_day++;
                      sp2->_month++;
                      mtx.unlock();
                  }
              });

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

    t1.join();
    t2.join();

    cout << sp1.use_count() << endl;
    cout << sp1.get() << endl;

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

循环引用(shared_ptr的缺点)

#pragma once

#include <thread>
#include <mutex>

// 1、RAII
// 2、像指针一样使用
// 3、拷贝问题
namespace qwy
{
    // 智能指针的类模板
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		// 保存资源
		// 构造函数
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
			, _pmtx(new mutex)
		{}


		// 析构函数
		// 释放资源
		~shared_ptr()
		{
			Release();
		}

		// 拷贝构造
		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			, _pmtx(sp._pmtx)
		{
			// 使用锁对++进行保护
			// 多个线程只能够串联执行
			_pmtx->lock(); 
			++(*_pcount);
			_pmtx->unlock();
		}

		void Release()
		{
			// 每个线程都有自己的独立栈结构,因此每个线程都有自己的flag,
			// 因此bool flag = false是没有线程安全问题的
			// 进程的堆空间,全局变量、静态变量是所有线程间共享的
			bool flag = false;

			_pmtx->lock();
			// *_pcount是堆上面的资源
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;

				// 当引用计数*_pcount为0时,此时我们不仅需要将_ptr和_pcount进行释放,
				// 还需要将锁进行释放,且必须是解锁之后,在释放锁,所以当*_pcount为0时
				// 将flag赋值为true,代表我们可以释放锁了
				flag = true;
			}
			_pmtx->unlock();

			if (flag == true)
			{
				delete _pmtx;
			}
		}

		// 赋值拷贝
		// sp1 = sp1;
		// sp1 = sp2;
		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;
		}

		int use_count() const
		{
			return *_pcount;
		}

		T* get() const
		{
			return _ptr;
		}

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

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

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

    // 链表的类
	struct ListNode
	{
		int val;
		shared_ptr<ListNode> _next;
		shared_ptr<ListNode> _prev;

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

	void test_shared_ptr2()
	{
        // n1和n2都是智能指针对象
		shared_ptr<ListNode> n1(new ListNode);
		shared_ptr<ListNode> n2(new ListNode);

        // n1->_next和n2->_prev 也是一个智能指针对象
        // 因此当赋值后,n1->_next和n2的引用计数都变为2
        // n2->_prev 和 n1同理
		n1->_next = n2;
		n2->_prev = n1;

		cout << n1.use_count() << endl;
		cout << n2.use_count() << endl;
	}
}
  • 打印结果
  • 通过打印结果我们可以得知,ListNode并没有被释放(没有打印析构函数中的~ListNode()

image-20230815192006140

  • 原因:循环引用

image-20230815191755696

weak_ptr(解决shared_ptr的循环引用问题)

  • 注:
#pragma once

#include <thread>
#include <mutex>

// 1、RAII
// 2、像指针一样使用
// 3、拷贝问题
namespace qwy
{
    // 智能指针的类模板(shared_ptr的类模板)
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		// 保存资源
		// 构造函数
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
			, _pmtx(new mutex)
		{}


		// 析构函数
		// 释放资源
		~shared_ptr()
		{
			Release();
		}

		// 拷贝构造
		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			, _pmtx(sp._pmtx)
		{
			// 使用锁对++进行保护
			// 多个线程只能够串联执行
			_pmtx->lock();
			++(*_pcount);
			_pmtx->unlock();
		}

		void Release()
		{
			// 每个线程都有自己的独立栈结构,因此每个线程都有自己的flag,
			// 因此bool flag = false是没有线程安全问题的
			// 进程的堆空间,全局变量、静态变量是所有线程间共享的
			bool flag = false;

			_pmtx->lock();
			// *_pcount是堆上面的资源
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;

				// 当引用计数*_pcount为0时,此时我们不仅需要将_ptr和_pcount进行释放,
				// 还需要将锁进行释放,且必须是解锁之后,在释放锁,所以当*_pcount为0时
				// 将flag赋值为true,代表我们可以释放锁了
				flag = true;
			}
			_pmtx->unlock();

			if (flag == true)
			{
				delete _pmtx;
			}
		}

		// 赋值拷贝
		// sp1 = sp1;
		// sp1 = sp2;
		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;
		}

		int use_count() const
		{
			return *_pcount;
		}

        // 拿到share_ptr管理的资源的指针
		T* get() const
		{
			return _ptr;
		}

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

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

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



    // weak_ptr的本质就是将share_ptr管理的资源进行管理
    // weak_ptr只负责管理,不负责释放这块资源
    // 释放资源还是由share_ptr在合适的时候进行释放
    // weak_ptr智能指针的类模板
	template<class T>
	class weak_ptr
	{
	public:
		// 构造函数
		weak_ptr()
			:_ptr(nullptr)
		{}

		// 拷贝构造
		// 通过sp.get()拿到shared_ptr管理的资源的指针,这样我们就可以对这块资源进行访问
		// 那么我们就需要对这块资源进行管理,也就不用释放这块资源了
		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;
		}
	public:
		T* _ptr;
	};

	struct ListNode
	{
		int val;
		// 可以指向资源/访问资源,不参与资源管理,不增加引用计数
		weak_ptr<ListNode> _next;
		weak_ptr<ListNode> _prev;

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

	void test_weak_ptr()
	{
        // n1和n2的引用计数都是1
		shared_ptr<ListNode> n1(new ListNode);
		shared_ptr<ListNode> n2(new ListNode);

        // n1->_next的类型是weak_ptr<ListNode>
        // 因此在赋值后,并不会增加n1的引用计数
		n1->_next = n2;
		n2->_prev = n1;
	}
    // 出函数的作用域后,先释放n1的资源,在n1中先释放n1的_next,这是一个weak_ptr的智能指针,只是将它管理的资源地址置空
}

// 打印结果为:
~ListNode()
~ListNode()
    // 说明将两个ListNode的资源都释放了

定制删除器

  • 演示1(正常运行结束)
#include<iostream>
#include<memory>
#include<string>

using namespace std;

int main()
{
    // 使用智能指针来管理这个string对象
	shared_ptr<string> sp2(new string);

	return 0;
}
  • 演示2(程序异常)
#include<iostream>
#include<memory>
#include<string>

using namespace std;

int main()
{
    // 使用智能指针来管理这个string对象
	shared_ptr<string> sp2(new string[10]);

	return 0;
}

image-20230815203711788

  • 这是因为,shared_ptr内部释放资源使用的是delete,而想要释放string[10],则需要使用delete[],因此需要为智能指针管理的资源定制删除器

  • 解决方案(定制删除器)

    • 使用定制删除器之后,就可以完美的完成资源的释放
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<memory>
#include<string>

using namespace std;

// 定制删除器的类模板
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>());

	// 使用lambda表达式作为定制删除器
	std::shared_ptr<string> sp3(new string[10], [](string* ptr){delete[] ptr; });
    // 传入关闭文件的定制删除器
	std::shared_ptr<FILE> sp4(fopen("Test.cpp", "r"), [](FILE* ptr){fclose(ptr); });

	return 0;
}

test_shared_ptr3()

#pragma once

#include <thread>
#include <mutex>

// 1、RAII
// 2、像指针一样使用
// 3、拷贝问题
namespace qwy
{
	// 默认删除器的类模板
	template<class T>
	class default_delete
	{
	public:
		void operator()(T* ptr)
		{
			delete ptr;
		}
	};

    // shared_ptr的类模板
	// class D = default_delete<T> 是定制删除器的缺省模板
	template<class T, class D = default_delete<T>>
	class shared_ptr
	{
	public:
		// RAII
		// 保存资源
		// 构造函数
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
			, _pmtx(new mutex)
		{}


		// 析构函数
		// 释放资源
		~shared_ptr()
		{
			Release();
		}

		// 拷贝构造
		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			, _pmtx(sp._pmtx)
		{
			// 使用锁对++进行保护
			// 多个线程只能够串联执行
			_pmtx->lock();
			++(*_pcount);
			_pmtx->unlock();
		}

		void Release()
		{
			// 每个线程都有自己的独立栈结构,因此每个线程都有自己的flag,
			// 因此bool flag = false是没有线程安全问题的
			// 进程的堆空间,全局变量、静态变量是所有线程间共享的
			bool flag = false;

			_pmtx->lock();
			// *_pcount是堆上面的资源
			if (--(*_pcount) == 0)
			{
				_del(_ptr);
				delete _pcount;

				// 当引用计数*_pcount为0时,此时我们不仅需要将_ptr和_pcount进行释放,
				// 还需要将锁进行释放,且必须是解锁之后,在释放锁,所以当*_pcount为0时
				// 将flag赋值为true,代表我们可以释放锁了
				flag = true;
			}
			_pmtx->unlock();

			if (flag == true)
			{
				delete _pmtx;
			}
		}

		// 赋值拷贝
		// sp1 = sp1;
		// sp1 = sp2;
		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;
		}

		int use_count() const
		{
			return *_pcount;
		}

		T* get() const
		{
			return _ptr;
		}

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

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

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

		// 定制删除器
		D _del;
	};

	// 定制删除器
	template<class T>
	struct DeleteArray
	{
		void operator()(const T* ptr)
		{
			delete[] ptr;
			cout << "delete [] " << ptr << endl;
		}
	};

	// 定制删除器
	struct Fclose
	{
		void operator()(FILE* ptr)
		{
			fclose(ptr);
			cout << " 文件关闭 " << ptr << endl;
		}
	};

	struct ListNode
	{
		int val;

		weak_ptr<ListNode> _next;
		weak_ptr<ListNode> _prev;

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

	void test_shared_ptr3()
	{
		// 使用的是默认删除器,可以完美的释放资源
		qwy::shared_ptr<ListNode> n1(new ListNode);

		// 使用默认删除器,是无法释放资源的
		// 使用我们自定义的定制删除器则可以完美的释放资源
		// 我们的shared_ptr的定制删除器和c++库的简化,因此使用方式不一致,但是效果是一样的
		qwy::shared_ptr<ListNode,DeleteArray<ListNode>> n2(new ListNode[10]);

		qwy::shared_ptr<FILE, Fclose> n3(fopen("Test.cpp", "r"));

		// 但是这种实现方式无法使用lambda表达式
	}
}
  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值