关于C++11中的make_shared

我在读《C++Primer(第五版)》智能指针的章节时,看到书中有如下内容

可见作者非常推荐使用make_shared来生成shared_ptr,那么我的疑问就来了,为什么make_shared是更好的方法?经过查阅相关资料和阅读源码,找出原因:make_shared只申请了一次内存,而普通的先new一个raw_ptr,再调用shared_ptr(raw_ptr)是进行了两次内存分配,所以一次分配要比两次分配效率高。

为了下文叙述方便,我们称呼下图中使用make_shared的构造方法为1,先new的方法为2。

shared_ptr<A> sha_a = make_shared<A>(); //方法1

A* a = new A;
shared_ptr<A> sha_b(a);  //方法2

既然make_shared节省了内存申请次数,那么看来我们始终都应该使用maked_shared而不应该是方法2了?答案是No!因为make_shared里面有坑,有可能导致对象的内存无法被释放!

回答这个问题前,让我们稍微深究一下C++11的shared_ptr。首先我们必须要认识一个东西:control block!这是什么玩意?和shared_ptr有什么关系?

原来C++11的shared_ptr正是依靠control block记录了强引用和弱引用的数量,从而实现引用计数清零时进行内存释放。只要有shared_ptr的存在,就一定要有一块control block记录引用数量,而这个control block就是所谓的方法2中的“二次内存申请”,换言之,只要用了shared_ptr,就必须为control block开辟一块内存空间。只要有强引用和弱引用的存在,control block就永远不会被释放,记住这一点,下面make_shared的坑就和这点有关。

如果是这样的话,那么按照常理猜想,就应该实例是一块内存空间,control block是一块内存空间,control block记录了实例的地址,从而控制实例的销毁。Good!我们猜想的与方法2实际做法完全一致,实例内存与control block内存的关系如下图

图中黄色是control block内存,橘黄色R是实例内存,当强引用计数为0时,R内存被释放,当弱引用也为0时,control block被释放。OK,方法2一切按照预期发展,没问题。

现在来到make_shared的情况,make_shared之所以能够做到一次内存申请就达到同时为实例和control block分配内存的目标,是因为make_shared把二者合并为了一块内存,申请时一并申请了!memory.h里面make_shared的代码为

template<class _Ty,
	class... _Types>
	_NODISCARD inline shared_ptr<_Ty> make_shared(_Types&&... _Args)
	{	// make a shared_ptr
	const auto _Rx = new _Ref_count_obj<_Ty>(_STD forward<_Types>(_Args)...);

	shared_ptr<_Ty> _Ret;
	_Ret._Set_ptr_rep_and_enable_shared(_Rx->_Getptr(), _Rx);
	return (_Ret);
	}

这个_Ref_count_obj就是一块连续空间,定义如下

template<class _Ty>
	class _Ref_count_obj
		: public _Ref_count_base
	{	// handle reference counting for object in control block, no allocator
public:
	template<class... _Types>
		explicit _Ref_count_obj(_Types&&... _Args)
		: _Ref_count_base()
		{	// construct from argument list
		::new (static_cast<void *>(&_Storage)) _Ty(_STD forward<_Types>(_Args)...);
		}

	_Ty * _Getptr()
		{	// get pointer
		return (reinterpret_cast<_Ty *>(&_Storage));
		}

private:
	virtual void _Destroy() noexcept override
		{	// destroy managed resource
		_Getptr()->~_Ty();
		}

	virtual void _Delete_this() noexcept override
		{	// destroy self
		delete this;
		}

	aligned_union_t<1, _Ty> _Storage;
	};

_Ref_count_obj里面有个成员_Storage,这个变量就是用来存储实例内存的,而_Ref_count_base就是control block,里面记录了强引用和弱引用的计数。

两个内存的逻辑关系如下图所示

从这张图也可以看出,control block和R被合并为了一块内存空间,哦,原来这样,怪不得申请一次就够了!But Wait,我们申请的是一个_Ref_count_obj,那我们释放了R后(也就是源码中的_Storage),怎么保证R的内存被真正归还了?答案是,R的内存逻辑上不存在了,但它其实并没有被归还,还和control block呆在一起,只是逻辑上触及不到而已。只有control block里面的弱引用也为0之后,control block被释放,R的内存才真正被一起归还。由此可见,即使强引用计数归0了,实例的内存也没有被释放掉(这个时候Object虽然调用了析构函数,但并不意味着内存被归还了),而这就是make_shared坑!

所以我们要记住一点,使用make_shared固然好,但请记住及时清理share_ptr和weak_ptr,只要还有一个引用,即使是弱引用,目标内存也是无法真正被释放的!

关于本文中的图片,来源于一个youtube视频,视频介绍了C++11的智能指针知识,值得一看,地址:C++ Smart Pointers

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值