当前位置: 首页 > news >正文

【C++智能指针】智能指针的发展和循环引用的原理和解决

目录

1.RAIl(智能指针的雏形)

2.拷贝导致的问题以及智能指针发展历史

2.1拷贝的问题(资源被析构两次)

2.2auto_ptr(资源权转移,不建议使用)

         2.3unique_ptr(防拷贝,在不需要拷贝的情况下使用) 

         2.4shared_ptr

2.4.2循环引用的问题(重点)

 3.定制删除器


智能指针是拿来处理异常安全问题的

  • 下面这段代码如果抛异常了,那么p1就指向的空间就没有被释放,内存泄漏 
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}

void func()
{
	int* p1 = new int;
	cout << div() << endl; // 异常安全的问题

	delete p1;
}

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

1.RAIl(智能指针的雏形)

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源,对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。(就是把原本资源托管给一个类对象)

优势:

  1. 不需要显式地释放资源。
  2. 采用这种方式,对象所需的资源在其生命期内始终保持有效(声明周期在调用栈帧里面,声明周期结束自动调析构函数)
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;
	}
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> sp1(new int);
	SmartPtr<int> sp2(new int);
	SmartPtr<int> sp3(new int);

	*sp1 = 10;
	cout << *sp1 << endl;
	(*sp1)++;
	(*sp1)++;
	cout << *sp1 << endl;

	cout << div() << endl;
}

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

	return 0;
}

 执行结果·:

解决了如果抛异常,在堆上开的空间得不到释放,导致的内存问题;

重载operator*和opertaor->,具有像指针一样的行为,可以正常访问

2.拷贝导致的问题以及智能指针发展历史

2.1拷贝的问题(资源被析构两次)

// RAII,把资源托管给一个类对象
// 用起来像指针一样
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;
	}
private:
	T* _ptr;
};

int main()
{
	//SmartPtr<int> sp1(new int);
	//SmartPtr<int> sp2(sp1);

	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);
	sp2 = sp1;

	return 0;
}

执行结果

  •  默认生成的拷贝构造,赋值重载都是浅拷贝,同一块空间被析构两次,程序奔溃

不能深拷贝的原因 

 2.2auto_ptr(资源权转移,不建议使用)

//auto_ptr
namespace LDJ
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
			//:_ptr(nullptr)
		{
			// 管理权转移
			sp._ptr = nullptr;
			//swap(_ptr, sp._ptr);
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}

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

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

// 结论:auto_ptr是一个失败设计
int main()
{
	LDJ::auto_ptr<int> sp1(new int);
	LDJ::auto_ptr<int> sp2(sp1); // 管理权转移
	// sp1悬空
	*sp2 = 10;
	cout << *sp1 << endl;
	cout << *sp2 << endl;

	return 0;
}

执行结果

  • sp1悬空了; 

2.3unique_ptr(防拷贝,在不需要拷贝的情况下使用) 

  • unique_ptr的实现原理:简单粗暴的防拷贝
namespace LDJ
{
	template<class T, class D = default_delete<T>>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;

			}
		}

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

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

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

	private:
		T* _ptr;
	};
}

2.4shared_ptr

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减 一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了
namespace LDJ
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pRefCount(new int(1))
		{}
		void AddRef()
		{
			 ++*(_pRefCount);
		}
		void Release()
		{

			if (--(*_pRefCount) == 0 && _ptr)
			{
				delete _ptr;
				delete _pRefCount;

			}
		}
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pRefCount(sp._pRefCount)
			, _pmtx(sp._pmtx)
		{
			AddRef();
		}

		~shared_ptr()
		{
			Release();
		}

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

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pRefCount;
	}
}

2.4.1operator=需要处理一下

shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (this->_pRefCount != sp._pRefCount)
			{
				Release();
				_ptr = sp._ptr;
				_pRefCount = sp._pRefCount;
				AddRef();
				return *this;
			}
		}

2.4.2循环引用的问题(重点)

struct Node
{
	~Node()
	{
		cout << "~Node" << endl;
	}
	int val;
	std::shared_ptr<Node> _next;
	std::shared_ptr<Node> _prev;
};
int main()
{
	std::shared_ptr<Node> sp1(new Node);
	std::shared_ptr<Node> sp2(new Node);
	sp1->_next = sp2;
	sp2->_prev = sp1;

	return 0;
}

执行结果:如果被析构了会打印两行~Node,然而并没有,说明sp1和sp2都没有被释放

 

循环引用的具体原因: 

解决循环引用weak_ptr

  • weak_ptr不是常规的智能指针,不增加计数支持访问,所以也不支持RAII 

struct Node
{
	~Node()
	{
		cout << "~Node" << endl;
	}
	int val;
	std::weak_ptr<Node> _next;
	std::weak_ptr<Node> _prev;
};
int main()
{
	std::shared_ptr<Node> sp1(new Node);
	std::shared_ptr<Node> sp2(new Node);
	cout << "sp1Count:" << sp1.use_count() << endl;
	cout << "sp2Count:" << sp2.use_count() << endl;
	sp1->_next = sp2;
	sp2->_prev = sp1;
	cout << "sp1Count:" << sp1.use_count() << endl;
	cout << "sp2Count:" << sp2.use_count() << endl;
	return 0;
}

 执行结果:前后sp1和sp2的计数都为1;所以可以证明weak_ptr不增加引用计数且解决了循环引用的问题;

 3.定制删除器

那么如果资源不是new出来的呢?比如:new[]、malloc、fopen,delete就不够用了

template<class T>
struct DeleteArray
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
	}
};
struct DeleteFile
{
	void operator()(FILE* ptr)
	{
		fclose(ptr);
	}
};
    unique_ptr<A> up1(new A);
    unique_ptr<A, DeleteArray<A>> up2(new A[10]);
    unique_ptr<FILE, DeleteFile> up3(fopen("test.txt", "w"));

相关文章:

  • 外包干了8天,技术退步明显.......
  • Pytorch:torch.stack 和 torch.as_tensor
  • android apk混淆
  • 全量知识系统 详细设计 祖传代码之 翻译器、解释器和编译器 暨 文档规范 之1
  • 从数据治理到数据资产管理,释放新质生产力
  • 静态路由表学习实验
  • 【前端的讲解】
  • 历史新知网:寄快递寄个电脑显示器要多少钱?
  • Android Studio六大基本布局详解
  • 安全防御-第六次
  • 小程序里.vue界面中传值的两种方式
  • 消息队列-RabbitMQ:延迟队列、rabbitmq 插件方式实现延迟队列、整合SpringBoot
  • 央企招聘:正式编制!八险三金!各项福利!中国邮政招人啦!
  • 欧拉公式 Euler‘s Formula
  • 0.安装和配置
  • redis我记不住的那些命令(六)
  • Spring - @PostConstruct 源码解析
  • JS 正则表达式常用方法
  • 2-分类问题 SVM 核函数
  • [附源码]计算机毕业设计校园订餐管理系统Springboot程序
  • GitLab CI/CD系列教程(一)
  • html当当书网站 html网上在线书城 html在线小说书籍网页 当当书城网页设计
  • [附源码]JAVA毕业设计课程网站设计(系统+LW)
  • Spring Boot TestEntityManager
  • 【@property的参数copy Objective-C语言】
  • 八股文之设计原则
  • C++图书管理系统(管理员-读者)
  • 高可用方案组件,Keepalived详解
  • MOSFET 和 IGBT 栅极驱动器电路的基本原理学习笔记(一)MOSFET技术
  • Ansible
  • 年产2万吨山楂酒工厂的设计-陈酿工段及车间的设计(lunwen+任务书+cad图纸)
  • Java项目:SSM教师师资管理系统