智能指针std::shared_ptr之循环引用

指针简介

智能指针std::shared_ptr简称为共享指针,智能指针std::weak_ptr简称为弱指针。

共享指针具有元素指针和引用计数基类指针,引用计数基类指针用以指向引用计数对象,而引用计数对象所属类继承自引用计数基类,具有元素指针或者元素空间。

引用计数基类具有两个计数器,分别是引用计数器和弱引用计数器。引用计数器控制元素的销毁或者析构时机,弱引用计数器控制引用计数对象的销毁时机。

函数简介

函数std::make_shared先创建引用计数对象,由引用计数对象构造元素,再创建临时共享指针,指向元素和引用计数对象,之后返回临时共享指针。

问题实质

类的成员变量包含共享指针,假设类的两个实例由共享指针管理,分别称为此实例与彼实例,此实例的共享指针指向彼实例,而彼实例的共享指针指向此实例。

上述情况可以视为,由共享指针管理的外层对象持有内层对象,而内层对象以共享指针形式直接或者间接引用外层对象。

销毁外层对象的前提是内层对象不再引用外层对象,而内层对象在析构之时才解除引用,即必须先销毁外层对象。似此对象之间跨层级相互引用,而无法解除引用关系,形成循环引用。

经典案例

倘若双向链表的指针都采用共享指针,精简代码如下所示:

#include <cstdlib>
#include <memory>
#include <iostream>

struct Node
{
	std::shared_ptr<Node> _prev;
	std::shared_ptr<Node> _next;

	~Node()
	{
		std::cout << "~Node" << std::endl;
	}
};

int main()
{
	auto head = std::make_shared<Node>();
	auto tail = std::make_shared<Node>();
	head->_next = tail;
	tail->_prev = head;
	return EXIT_SUCCESS;
}

问题描述

在退出函数之时,局部变量因超出作用域而结束生命周期,依次销毁共享指针tail和head。

在析构tail之时,head所指节点的后继仍指向tail所指节点及其引用计数对象,导致引用计数对象的两个计数器都未归零,即tail所指节点及其引用计数对象无法销毁。

在析构head之时,由于tail所指节点未被销毁,而节点的前驱仍指向head所指节点及其引用计数对象,同样导致引用计数对象的两个计数器未归零,即head所指节点及其引用计数对象无法销毁。

所有节点及其引用计数对象未正常销毁,最终导致资源泄漏。

解决方案

节点的后继采用共享指针,而节点的前驱采用弱指针,如此便可打破循环引用。

不过,即使采用上述方案,双向循环链表仍然存在循环引用问题。

升阶案例

Eterfree线程池ThreadPool的简化代码如下所示:

#include <cstdlib>
#include <functional>
#include <memory>
#include <iostream>

class ThreadPool final
{
	struct Structure
	{
		std::function<void()> _fetch;

		~Structure()
		{
			std::cout << "~Structure" << std::endl;
		}
	};

private:
	std::shared_ptr<Structure> _data;

public:
	ThreadPool() : \
		_data(std::make_shared<Structure>())
	{
		_data->_fetch = [_data] {};
	}
};

int main()
{
	ThreadPool threadPool;
	return EXIT_SUCCESS;
}

问题描述

构造ThreadPool对象之时,先在构造函数体外的初始化列表之中,调用函数std::make_shared,创建指向Structure实例的共享指针,用以构造成员变量_data。再于构造函数体内,以Lambda表达式初始化Structure成员变量_fetch。在Lambda的捕获列表之中,以成员变量_data复制构造非局部变量_data,二者共同指向Structure实例及其引用计数对象。

析构ThreadPool对象之时,也会析构成员变量_data,由于存在非局部变量_data,引用计数对象的两个计数器都未归零,即Structure实例及其引用计数对象都未销毁。而Structure成员变量_fetch亦未析构,因此非局部变量_data无法析构,最终导致资源泄漏。

解决方案

寻找以下案例代码:

_data->_fetch = [_data] {};

改为下述代码:

_data->_fetch = [_data = std::weak_ptr(_data)] {};

在Lambda的捕获列表之中,非局部变量_data由共享指针对象改为弱指针对象。共享指针同时维护引用计数对象的引用计数器和弱引用计数器,而弱指针只维护引用计数对象的弱引用计数器。

在析构ThreadPool成员变量_data之时,即使存在非局部变量_data,引用计数对象的引用计数器也会归零,于是能够析构成员变量_data指向的Structure实例。而析构Structure实例,自然会析构函数子_fetch,当然也会析构非局部变量_data,使得引用计数对象的弱引用计数器归零,从而销毁引用计数对象。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值