llvm:: IntrusiveRefCntPtr

boost::intrusive_ptr

在介绍llvm::IntrusiveRefCntPtr之前,先介绍intrusive_ptr的概念,intrusive_ptr也是智能指针的一种,同样避免内存泄漏的安全问题,并且intrusive_ptr也是基于RAII实现的。但intrusive_ptrshared_ptr也有一些不同,最本质不同就是计数器存放的位置。对于std::shared_ptr,计数器并不是对象的数据成员,而是std::shared_ptr的数据成员,如下图所示,当然有时候Object和count所占用的内存是一起分配的,例如当使用std::make_shared的时候。
shared_ptr

intrusive_ptr中的计数器是作为对象的数据成员存在的,并且intrusive_ptr只是提供了增减计数器的接口。intrusive_ptr中计数器与对象的示意图如下:
这里写图片描述

为了能够使count作为对象的数据成员存在,一种可行的实现方案是使所有欲使用instrusive_ptr的对象继承自一个专门"提供"count的类型,这个类型在boost中就是basic_intrusive_ref_counter,如下:

namespace boost {
	struct thread_unsafe_counter;
	struct thread_safe_counter;
	template<class DrivedT, class CounterPolicyT = thread_safe_counter>
	class intrusive_ref_counter
	{
	public:
		intrusive_ref_counter() = noexecpt;
		intrusive_ref_counter(intrusive_ref_counter *r) = noexcept;

		intrusive_ref_counter & operator=(intrusive_ref_counter const & r) noexcept;

		unsigned int use_count() const noexcept;
	protected:
		~intrusive_ref_counter() = default;
	};
}

提供count的任务从智能指针转移到boost::intrusive_ref_counter,那么boost::intrusive_ptr主要就是进行做好计数的工作,关于boost::intrusive_ptr的描述如下:

The intrusive_ptr class template stores a pointer to an object with an embedded reference count. Every new intrusive_ptr instance increments the reference count by using an unqualified call to the function intrusive_ptr_add_ref, passing it the pointer as an argument. Similarly, when an intrusive_ptr is destroyed, it calls intrusive_ptr_release; this function is responsible for destroying the object when its reference count drops to zero. The user is expected to provide suitable definitions of these two functions.

从上面的描述中我们得到如下几点信息:

  • intrusive_ptr本质上存储的是一个指向对象的指针,其中这个对象“内嵌“了计数器
  • 每当intrusive_ptr由对象A创建时,就将对象A的计数加一
  • 每当intrusive_ptr销毁时,其创建时对象的计数就减一,当计数减到0时,将该对象销毁
  • 其中计数加一和减一的函数,intrusive_ptr_add_ref()intrusive_ptr_release()的实现由用户提供

instrusive_ptr的声明大致如下:

namespace boost {
	template<class T> element_type;

	intrusive_ptr(); // never throws
	intrusive_ptr(T *p, bool add_ref = true);

	intrusive_ptr(intrusive_ptr const & r)l
	template<class Y> intrusive_ptr(intrusive_ptr<T> const &r);

	~intrusive_ptr();
	
	T & operator*() const;
	T *operator->() const;
	T *get() const;
	T *detach();
};

其中对于上述的构造函数和析构函数,细节如下:

intrusive_ptr(T * p, bool add_ref = true);
Effects: if (p != 0 && add_ref) intrusive_ptr_add_ref(p);
Postconditions: get() == p

intrusive_ptr(intrusive_ptr const & r);
template<class Y> intrusive_ptr(intrusive_ptr<Y> const & r);
Effects: if (r.get() != 0) intrusive_ptr_add_ref(r.get());
Postconditions: get() == r.get()

~intrusive_ptr();
Effects: if(get() != 0) intrusive_ptr_release(get())

另外对于intrusive_ptr_add_ref()intrusive_ptr_release(),如果编译器支持ADL(Argument Dependent Lookup),就会在它们的参数所定义的namespace中查找二者的定义,否早它们必须定义在boost namespace中。

intrusive_ptr vs std::shared_ptr

intrusive_ptr class template中有如下两段描述,第一段描述使用intrusive_ptr的主要原因,第二段说明在intrusive_ptrstd::shared_ptr相同的情况下,尽量使用std::shared_ptr

The main reasons to use intrusive_ptr are:

  • Some existing frameworks or Oses provide objects with embedded reference counts;
  • The memory footprint of intrusive_ptr is the same as the corresponding raw pointer;
  • intrusive_ptr<T> can be constructed from an arbitrary raw pointer of type T*.

As a general rule, if it isn’t obvious whether intrusive_ptr better fits your needs than shared_ptr, try a shared_ptr-based design first.

对于第一段中第二个原因需要说明一下,为什么intrusive_ptr占用的内存比shared_ptr少,原因在于std::shared_ptr理念上对象和计数的存放是分开的,所以大部分情况下每个std::shared_ptr有两个指针的内存开销,一个指针指向control block(存储引用计数等信息),一个指针指向对象。之所以说大部分情况,是因为std::make_shared<>会统一分配一整块内存存储计数和对象,此时就只需存储一个指针。

Memory footprint refers to the amount of main memory that a program uses or references while running. [Memory footprint]

std::shared_ptr中的std::make_shared<>

前面提到了std::make_shared<>会为计数和对象分配一整块内存,虽然标准中没有这样规定,但是由于std::make_shared提供了这样的一个将计数和对象放在一起的机会,所以基本上所有的实现都会这样做。

std::shared_ptr<T>(new T(args…)) performs at least two allocations (one for the object T and one for the control block of the shared pointer), while std::make_shared<T> typically performs only one allocation (the standard recommends, but does not require this, all known implementations do this). [make_shared]

The storage is typically larger than sizeof(T) in order to use one allocation for both the control block of the shared pointer and the T object. The std::shared_ptr constructor called by this function enables shared_from_this with a pointer to the newly constructed object of type T. [make_shared]

std::make_shared所带来的好处和坏处都是由这一个特点引发的,通常意义上使用std::make_shared是好的,不仅包裹了内存的分配,不会将raw pointer暴露出来,避免了访问已释放内存的问题,同时也减少了内存的分配。stackoverflow上有一个相关的问题,见Difference in make_shared and normal shared_ptr in C++,比较详尽。

了解了std::make_shared的好处以后,intrusive_ptr的第三个优点也就显而易见了。使用一个raw pointer来构造std::shared_ptr通常不能保证是安全的,反而更容易出错,因为raw pointer有可能在其他地方被使用,而std::shared_ptr不能为这些使用增加计数或减少计数。而intrusive_ptr就不会存在这个问题,因为raw pointer本身就存储有计数信息。

知乎上有一个intrusive_ptr的问题,为什么 intrusive_ptr 没有进入标准库?,该问题下设提到了std::enable_shared_from_this,由于我在工作中没有遇到很多使用std::enable_shared_from_this的代码场景,理解不够深刻,所以先挖个坑。

llvm::IntrusiveRefCntPtr

待填

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值