智能指针的辅助函数 std::make
前面已经提到std::shared_ptr
有三个缺陷:
shared_ptr
对象共用引用计数器,计数器本身需通过new
放在堆上。而new
会引起性能问题。- 引用计数的内存区域和数据区域不一致,缓存失效导致性能问题。
- 编写代码不善,将导致同一个数据,绑定到了两个引用计数,从而导致双重删除。
boost::instrusive_ptr
可以解决这些问题。但std::shared_ptr
也提供了配套函数来试图解决。这就是std::make_shared
和std::enable_shared_from_this
。
1. std::make_shared
std::make_shared
的作用是在为数据分配空间的同时,分配计数器的空间(从实际实现是反过来,在分配计数器空间的空时,多分配一块空间给数据用),让数据和计数器在连续的内存区域,对
CPU 缓存也友好。同时不用暴露原始指针,也降低了第三个问题的出现几率:
auto xp = std::make_shared<int>(1);
它的实现依赖于shared_ptr
的构造函数:
template<typename _Tp, typename... _Args>
inline shared_ptr<_Tp> make_shared(_Args&&... __args) {
typedef typename std::remove_const<_Tp>::type _Tp_nc;
return __shared_ptr<_Tp>(_Sp_make_shared_tag(), std::allocator<_Tp_nc>(),
std::forward<_Args>(__args)...);
}
这里_SP_make_shared_tag
是一个空的helper
类,只是为了区别构造函数的不同重载。实际调用的构造函数如下:
template<typename _Alloc, typename... _Args>
__shared_ptr(_Sp_make_shared_tag __tag, const _Alloc& __a, _Args&&... __args)
: _M_ptr(), _M_refcount(__tag, (_Tp*)0, __a, std::forward<_Args>(__args)...)
{
void* __p = _M_refcount._M_get_deleter(typeid(__tag));
_M_ptr = static_cast<_Tp*>(__p);
_M_enable_shared_from_this_with(_M_ptr);
}
实际内存分配发生在_M_refcount
的构造函数:
template<typename _Tp, typename _Alloc, typename... _Args>
__shared_count(_Sp_make_shared_tag, _Tp*, const _Alloc& __a, _Args&&... __args) : _M_pi(0) {
typedef _Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp> _Sp_cp_type;
typename _Sp_cp_type::__allocator_type __a2(__a);
auto __guard = std::__allocate_guarded(__a2);
_Sp_cp_type* __mem = __guard.get();
::new (__mem) _Sp_cp_type(std::move(__a), std::forward<_Args>(__args)...);
_M_pi = __mem;
__guard = nullptr;
}
构造原始数据则藏在_Sp_counted_ptr_inplace
构造函数中。这个类是计数器的基类,里面不光包含了计数器,也包含了数据对象:
template<typename _Tp, typename _Alloc, _Lock_policy _Lp>
class _Sp_counted_ptr_inplace final : public _Sp_counted_base<_Lp> {
// actual data object
struct _Impl : _Sp_ebo_helper<0, _Alloc> {
__gnu_cxx::__aligned_buffer<_Tp> _M_storage;
};
_Impl _M_impl;
public:
template<typename... _Args>
_Sp_counted_ptr_inplace(_Alloc __a, _Args&&... __args) : _M_impl(__a) {
allocator_traits<_Alloc>::construct(__a, _M_ptr(), std::forward<_Args>(__args)...);
}
}
所以make_shared
将导致数据的内存空间合并到了计数器。而我们知道数据的存续期将早于计数器,这使得即使数据被销毁,如果计数器还没销毁,数据的内存空间将不会被释放!这和boost::instrusive
的设计完全相反。
2. std::enable_shared_from_this
std::enable_shared_from_this
是一种侵入式设计,和boost::instrusive
差不多:
class T : public std::enable_shared_from_this {
public:
int age;
std::string name;
};
T* t = new T();
// 下面的shared1, shared2和weak都使用同一个计数器。
std::shared_ptr<T> shared1 = t;
// 只有t已经被一个shared_ptr托管时,shared_from_this()才有效,否则将抛出异常。
std::shared_ptr<T> shared2 = t->shared_from_this();
std::weak_ptr<T> weak = t->weak_from_this();
其中std::enable_shared_from_this
的实现很简单:
template<typename _Tp>
class enable_shared_from_this
{
public:
shared_ptr<_Tp> shared_from_this()
{ return shared_ptr<_Tp>(this->_M_weak_this); }
shared_ptr<const _Tp> shared_from_this() const
{ return shared_ptr<const _Tp>(this->_M_weak_this); }
weak_ptr<_Tp> weak_from_this() noexcept
{ return this->_M_weak_this; }
weak_ptr<const _Tp> weak_from_this() const noexcept
{ return this->_M_weak_this; }
protected:
template<typename _Tp1>
void _M_weak_assign(_Tp1* __p, const __shared_count<>& __n) const noexcept
{ _M_weak_this._M_assign(__p, __n); }
private:
mutable weak_ptr<_Tp> _M_weak_this;
};
注意这里的_M_weak_this
默认没有做初始化,因此数据没有被托管时,shared_from_this()
无效。但一旦做了初始化,比如执行了std::shared_ptr<int>
shared1 = t
时,将调用下面初始化函数:
template<typename _Yp, typename = _SafeConv<_Yp>>
explicit __shared_ptr(_Yp* __p) : _M_ptr(__p), _M_refcount(__p, typename is_array<_Tp>::type())
{
_M_enable_shared_from_this_with(__p);
}
而_M_enable_shared_from_this_with
则负责将计数器写入数据对象中:
// if _Yp base on std::enable_shared_from_this.
template<typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
typename enable_if<__has_esft_base<_Yp2>::value>::type _M_enable_shared_from_this_with(_Yp* __p) noexcept {
if (auto __base = __enable_shared_from_this_base(_M_refcount, __p))
__base->_M_weak_assign(const_cast<_Yp2*>(__p), _M_refcount);
}
}
从实现上看,std::enable_shared_from_this
效率远没有boost::instrusive_ptr
高。因为侵入数据的只是计数器的指针,并不是计数器本身。
Tips: Until now, everytime you want to store your article, we will help you store it in Filecoin network. In the future, you can store it in Filecoin network using your own filecoin.
Support author:
Author's Filecoin address:
Or you can use Likecoin to support author: