智能指针一些实现分析
提供值传递但是指针语义的功能。通过指针占用并且对管理对象,在离开作用域时释放该对象。
在使用上还有另外一个很好用的功能,精简了代码复杂度,管理的对象类可以省略以下的函数
- 默认构造函数
- 复制构造函数
- 复制赋值函数
比如有一个类 Fd 用于管理 fd ,并且拥有 fd 的所有权,所以 Fd 一定需要包含一个 fd 实体及不应该被复制。
所以需要实现三个函数
- 普通的 fd 构造函数
- 移动构造函数
- 析构函数
cpp
struct Fd {
Fd() = delete;
Fd(const Fd &) = delete;
Fd &operator=(const Fd &) = delete;
Fd(int fd) : fd_(fd) { }
Fd(Fd &&other) : fd_(other.fd_) { other.fd_ = -1; }
~Fd() {
if (fd_ != -1)
::close(fd_);
}
int fd_;
};
如果 Fd 内几个函数被 delete ,在使用 std::map 的情况下,编译是不通过的。
cpp
std::map<std::string, Fd> fdmap;
auto x = fdmap["x"];
未实现默认构造函数的报错如下:
txt
test.cpp:78:23: required from here
/usr/include/c++/12/tuple:1818:9: error: no matching function for call to 'Fd::Fd()'
1818 | second(std::forward<_Args2>(std::get<_Indexes2>(__tuple2))...)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在实现了 移动构造函数 但是未实现 复制构造函数 的情况下,默认不会生成 复制构造函数 ,报错如下:
txt
test.cpp: In function 'int main()':
test.cpp:78:23: error: use of deleted function 'constexpr Fd::Fd(const Fd&)'
78 | auto x = fdmap["x"];
test.cpp:7:8: note: 'constexpr Fd::Fd(const Fd&)' is implicitly declared as deleted because 'Fd' declares a move constructor or move assignment operator
使用智能指针进行包装一下,强化了类的设计,一定只能通过一个 fd 来构造一个 Fd 。
cpp
std::map<std::string, std::shared_ptr<Fd>> fd_ptr_map;
fd_ptr_map.emplace("x", std::make_shared<Fd>(2));
auto x1 = fd_ptr_map["x"];
if (x1)
std::cout << x1->fd_ << std::endl; // 2
也能同样引用在多态的场景
cpp
struct B {};
struct D : public B {};
void fn(std::shared_ptr<B> p) {}
auto p = std::make_shared<D>();
fn(p); // ok
std::unique_ptr
可以算是从 auto_ptr 演化而来的智能指针,和 auto_ptr 最大的不同为 unque_ptr 不支持复制操作,语义上是唯一的(符合命名)。
一些其他的特性支持
- 自定义删除器
- 移动语义
在细看 unique_ptr 的实现之前,先看一下 auto_ptr 及其被废弃的原因。
std::auto_ptr(C++17废弃)
std::auto_ptr 是最早的智能指针,对于所有权的管理比较原始,和语义的基础设施也有点关系。
被废弃的最大原因,auto_ptr 支持赋值操作,旧的指针被置为 0(有点类似移动语义)。
代码的实现如下:
cpp
auto_ptr &operator=(auto_ptr &__a) throw() {
reset(__a.release());
return *this;
}
element_type *release() throw() {
element_type *__tmp = _M_ptr;
_M_ptr = 0;
return __tmp;
}
void reset(element_type *__p = 0) throw() {
if (__p != _M_ptr) {
delete _M_ptr;
_M_ptr = __p;
}
}
这样带来的后果就是之前的 auto_ptr 还能够继续使用,没有限制,非常容易造成问题。
cpp
auto p2 = std::auto_ptr<int>(new int(3));
auto p3 = p2;
std::cout << *p3 << std::endl; // 3
std::cout << *p2 << std::endl; // Segmentation fault
而且命名上语义不清晰,估计标准委员会也懒得改了,配合 C++11 新增的 delete
关键字和移动语义,直接出一个语义更清晰的 unique_ptr 。
细节
通过 C++11 新增的关键字 delete
将复制的动作删除
cpp
// Disable copy from lvalue.
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
在析构函数内通过删除器对对象进行删除,这样就可以自定义删除了。
cpp
~unique_ptr() noexcept {
static_assert(__is_invocable<deleter_type &, pointer>::value,
"unique_ptr's deleter must be invocable with a pointer");
auto &__ptr = _M_t._M_ptr();
if (__ptr != nullptr)
get_deleter()(std::move(__ptr));
__ptr = pointer();
}
/// Calls `delete __ptr`
_GLIBCXX23_CONSTEXPR
void operator()(_Tp *__ptr) const {
static_assert(!is_void<_Tp>::value, "can't delete pointer to incomplete type");
static_assert(sizeof(_Tp) > 0, "can't delete pointer to incomplete type");
delete __ptr;
}
另外还提供了一个 operator bool 函数,可以使用类似普通指针的判断 if (p) { }
cpp
explicit operator bool() const noexcept {
return get() == pointer() ? false : true;
}
支持移动语义
cpp
auto p1 = std::make_unique<Fd>(2);
auto p2(std::move(p1));
if (p2)
std::cout << p2->fd_ << std::endl; // 2
// 实现
unique_ptr(unique_ptr<_Up, _Ep> &&__u) noexcept
: _M_t(__u.release(), std::forward<_Ep>(__u.get_deleter())) {}
std::shared_ptr
内部提供了引用计数的智能指针,支持复制的语义,在计数为 0 的时候,对象被释放。
某些场景下 unique_ptr 可能使用受限
cpp
std::map<std::string, std::unique_ptr<int>> m;
m.emplace("x", std::make_unique<int>(10));
auto p = m["x"];
// 编译失败
test.cpp: In function 'int main()':
test.cpp:65:19: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
65 | auto p = m["x"];
| ^
In file included from /usr/include/c++/12/memory:76,
from test.cpp:3:
/usr/include/c++/12/bits/unique_ptr.h:514:7: note: declared here
514 | unique_ptr(const unique_ptr&) = delete;
| ^~~~~~~~~~
这个时候就需要使用 shared_ptr 来解决这个问题,其内部 operator= 实现为计数加 1
cpp
__shared_count(const __shared_count &__r) noexcept : _M_pi(__r._M_pi) {
if (_M_pi != nullptr)
_M_pi->_M_add_ref_copy();
}
void _M_add_ref_copy() { __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }
析构函数的最终实现为,计数-1,如果为最后一个计数,那么就进行释放。
cpp
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
_M_release_last_use();
实现
通过 gdb 跟踪一下 std::make_shared 的调用链,停在调用 enable_from_this 的地方。
cpp
template <typename _Tp, typename... _Args>
inline shared_ptr<_NonArray<_Tp>> make_shared(_Args && ...__args) {
using _Alloc = allocator<void>;
_Alloc __a;
return shared_ptr<_Tp>(_Sp_alloc_shared_tag<_Alloc>{__a}, std::forward<_Args>(__args)...);
}
class shared_ptr {
private:
template <typename _Yp, typename... _Args>
friend shared_ptr<_NonArray<_Yp>> make_shared(_Args &&...);
};
template <typename _Alloc, typename... _Args>
shared_ptr(_Sp_alloc_shared_tag<_Alloc> __tag, _Args &&...__args)
: __shared_ptr<_Tp>(__tag, std::forward<_Args>(__args)...) {}
template <typename _Alloc, typename... _Args>
__shared_ptr(_Sp_alloc_shared_tag<_Alloc> __tag, _Args &&...__args)
: _M_ptr(), _M_refcount(_M_ptr, __tag, std::forward<_Args>(__args)...) {
_M_enable_shared_from_this_with(_M_ptr);
}
把其中出现的数据结构抽出来,内部有两个变量分别是元素的指针和引用计数结构体。另外继承了一个结构体 __shared_ptr_access .
cpp
template <typename _Tp>
class shared_ptr : public __shared_ptr<_Tp> {
};
enum _Lock_policy { _S_single, _S_mutex, _S_atomic };
static const _Lock_policy __default_lock_policy = _S_atomic;
template <typename _Tp, _Lock_policy _Lp = __default_lock_policy>
class __shared_ptr : public __shared_ptr_access<_Tp, _Lp> {
private:
element_type *_M_ptr; // Contained pointer.
__shared_count<_Lp> _M_refcount; // Reference counter.
};
__shared_ptr_access
提供了 shared_ptr 的 *
和 ->
操作。一般情况下使用的类型为
cpp
__shared_ptr_access<int, (__gnu_cxx::_Lock_policy)2, false, false>
实现为通过继承的方式,在父类中调用子类的成员函数。
cpp
// Define operator* and operator-> for shared_ptr<T>.
template <typename _Tp, _Lock_policy _Lp, bool = is_array<_Tp>::value, bool = is_void<_Tp>::value>
class __shared_ptr_access {
public:
using element_type = _Tp;
element_type &operator*() const noexcept {
__glibcxx_assert(_M_get() != nullptr);
return *_M_get();
}
element_type *operator->() const noexcept {
_GLIBCXX_DEBUG_PEDASSERT(_M_get() != nullptr);
return _M_get();
}
private:
element_type *_M_get() const noexcept {
return static_cast<const __shared_ptr<_Tp, _Lp> *>(this)->get();
}
};
__shared_count
__shared_count 为智能指针的核心实现,包含一个成语变量 _M_pi ,为 _Sp_counted_base 指针类型.
cpp
template<_Lock_policy _Lp>
class __shared_count {
private:
_Sp_counted_base<_Lp>* _M_pi;
}
_Sp_counted_base
内部定义了基本的引用计数的操作成员函数,和对象释放的虚函数接口。
包含两个变量,加上虚表 _Sp_counted_base 的大小为 16 个字节。
cpp
_Atomic_word _M_use_count; // #shared
_Atomic_word _M_weak_count; // #weak + (#shared != 0)
_Sp_counted_ptr_inplace
继承了 _Sp_counted_base ,对象的内存申请和构造在这个类中实现
cpp
template <typename _Tp, typename _Alloc, _Lock_policy _Lp>
class _Sp_counted_ptr_inplace final : public _Sp_counted_base<_Lp> {
using __allocator_type = __alloc_rebind<_Alloc, _Sp_counted_ptr_inplace>;
// Alloc parameter is not a reference so doesn't alias anything in __args
template <typename... _Args>
_Sp_counted_ptr_inplace(_Alloc __a, _Args &&...__args) : _M_impl(__a) {
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 2070. allocate_shared should use allocator_traits<A>::construct
allocator_traits<_Alloc>::construct(__a, _M_ptr(),
std::forward<_Args>(__args)...); // might throw
}
~_Sp_counted_ptr_inplace() noexcept { }
class _Impl {
__gnu_cxx::__aligned_buffer<_Tp> _M_storage;
};
_Impl _M_impl;
};
_M_impl 中存放的是对象的内存,对象的内存随着析构函数释放。
另外实现了 _M_dispose 和 _M_destroy 两个虚函数.
code
cpp
virtual void _M_dispose() noexcept {
allocator_traits<_Alloc>::destroy(_M_impl._M_alloc(), _M_ptr());
}
// Override because the allocator needs to know the dynamic type
virtual void _M_destroy() noexcept {
__allocator_type __a(_M_impl._M_alloc());
__allocated_ptr<__allocator_type> __guard_ptr{__a, this};
this->~_Sp_counted_ptr_inplace();
}
__shared_count 的实现
在 构造函数 内构造对象(及指针)和引用计数。
cpp
template <typename _Tp, typename _Alloc, typename... _Args>
__shared_count(_Tp *&__p, _Sp_alloc_shared_tag<_Alloc> __a, _Args &&...__args) {
typedef _Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp> _Sp_cp_type;
typename _Sp_cp_type::__allocator_type __a2(__a._M_a);
auto __guard = std::__allocate_guarded(__a2);
_Sp_cp_type *__mem = __guard.get();
auto __pi = ::new (__mem) _Sp_cp_type(__a._M_a, std::forward<_Args>(__args)...);
__guard = nullptr;
_M_pi = __pi;
__p = __pi->_M_ptr(); // 设置对象指针
}
在 析构函数 内释放对象(引用计数),最终调用 _Sp_counted_ptr_inplace::_M_release
cpp
~__shared_count() noexcept {
if (_M_pi != nullptr)
_M_pi->_M_release();
}
template <>
inline void _Sp_counted_base<_S_atomic>::_M_release() noexcept {
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
// ...
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1) {
_M_release_last_use(); // 调用析构函数
}
}
void _M_release_last_use_cold() noexcept { _M_release_last_use(); }
void _M_release_last_use() noexcept {
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
_M_dispose();
// ...
}
// 后面就是虚函数 _Sp_counted_base::_M_dispose 了
在 复制构造函数 内增加引用计数
cpp
__shared_count(const __shared_count &__r) noexcept : _M_pi(__r._M_pi) {
if (_M_pi != nullptr)
_M_pi->_M_add_ref_copy();
}
// _Sp_counted_base
void _M_add_ref_copy() { __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }
线程安全
_Sp_counted_base 额外对 锁协议(lock policy) 进行特化实现了三套接口,对引用计数的操作保证线程安全,但是后续取出来的指针的线程安全需要用户去保证。
cpp
template <>
inline bool _Sp_counted_base<_S_single>::_M_add_ref_lock_nothrow() noexcept {
if (_M_use_count == 0)
return false;
++_M_use_count;
return true;
}
template <>
inline bool _Sp_counted_base<_S_mutex>::_M_add_ref_lock_nothrow() noexcept {
__gnu_cxx::__scoped_lock sentry(*this);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, 1) == 0) {
_M_use_count = 0;
return false;
}
return true;
}
template <>
inline bool _Sp_counted_base<_S_atomic>::_M_add_ref_lock_nothrow() noexcept {
// Perform lock-free add-if-not-zero operation.
_Atomic_word __count = _M_get_use_count();
do {
if (__count == 0)
return false;
// Replace the current counter value with the old value + 1, as
// long as it's not changed meanwhile.
} while (!__atomic_compare_exchange_n(&_M_use_count, &__count, __count + 1, true,
__ATOMIC_ACQ_REL, __ATOMIC_RELAXED));
return true;
}
unique_ptr 到 shared_ptr 转换
unique_ptr 可以转换为 shared_ptr ,需要复制对象内存指针和 移动 删除器,最终实现为
cpp
template <typename _Yp, typename _Del, typename = _UniqCompatible<_Yp, _Del>>
__shared_ptr(unique_ptr<_Yp, _Del> &&__r) : _M_ptr(__r.get()), _M_refcount() {
auto __raw = __to_address(__r.get());
_M_refcount = __shared_count<_Lp>(std::move(__r));
_M_enable_shared_from_this_with(__raw);
}
// Special case for unique_ptr<_Tp,_Del> to provide the strong guarantee.
template <typename _Tp, typename _Del>
explicit __shared_count(std::unique_ptr<_Tp, _Del> &&__r) : _M_pi(0) {
// ...
_Alloc __a;
_Sp_cd_type *__mem = _Alloc_traits::allocate(__a, 1);
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 3548. shared_ptr construction from unique_ptr should move (not copy) the deleter
_Alloc_traits::construct(__a, __mem, __r.release(), std::forward<_Del>(__r.get_deleter()));
_M_pi = __mem;
}
std::weak_ptr
cppreference 的描述是对被 std::shared_ptr 管理的对象存在非拥有性(「弱」)引用,表达临时所有权的概念,在访问所引用的对象前必须先转换为 std::shared_ptr。
一个简单的例子来演示循环引用 shared_ptr 导致资源未释放
cpp
class ClassB;
class ClassA {
public:
std::shared_ptr<ClassB> b_ptr;
ClassA(std::shared_ptr<ClassB> b) : b_ptr(b) {}
~ClassA() { std::cout << "ClassA destructed" << std::endl; }
};
class ClassB {
public:
std::shared_ptr<ClassA> a_ptr;
ClassB(std::shared_ptr<ClassA> a) : a_ptr(a) {}
~ClassB() { std::cout << "ClassB destructed" << std::endl; }
};
int main() {
auto a = std::make_shared<ClassA>(nullptr);
auto b = std::make_shared<ClassB>(a);
a->b_ptr = b;
// 此时,a 和 b 形成循环引用
// 程序结束时,它们不会被释放,因为引用计数不会降为0
return 0;
}
引用计数在程序退出的时候分别为 1 ,如果在类中使用 weak_ptr 来代替 shared_ptr ,那么引用计数不会增加,将打破循环引用。
a 和 b 的所有权属于 main 函数,而内部的 a 和 b 属于一个观察者的角色。
一个经典的观察者模式的 demo 代码,演示了如何使用 weak_ptr 扮演一个观察者的角色,此刻的 shared_ptr 的所有权只属于 main 函数。
cpp
class Observer {
public:
virtual ~Observer() = default;
virtual void update(int state) = 0;
};
class Foo {
public:
Foo() { std::cout << "Foo::Foo" << std::endl; }
~Foo() { std::cout << "Foo::~Foo observers_.size=" << observers_.size() << std::endl; }
void attach(std::shared_ptr<Observer> observer) { observers_.push_back(observer); }
void detach(const Observer *observer) {
observers_.erase(std::remove_if(observers_.begin(), observers_.end(),
[&observer](std::weak_ptr<Observer> ob_weak) {
return ob_weak.lock().get() == observer;
}));
}
void notify(int state) const {
for (auto ob_weak : observers_)
ob_weak.lock()->update(state);
}
private:
std::vector<std::weak_ptr<Observer>> observers_;
};
class FooObserver : public Observer {
public:
static std::shared_ptr<FooObserver> create(int fd, std::shared_ptr<Foo> foo) {
auto observer = std::make_shared<FooObserver>(fd, foo);
foo->attach(observer); // 完成初始化
return observer;
}
explicit FooObserver(int id, std::shared_ptr<Foo> foo) : id_(id), foo_(foo) {
std::cout << "FooObserver::FooObserver() Update id=" << id_
<< ",foo_.use_count=" << foo_.use_count() << std::endl;
}
~FooObserver() {
std::cout << "FooObserver::~FooObserver id=" << id_
<< ", foo_.use_count=" << foo_.use_count() << std::endl;
foo_.lock()->detach(this);
}
void update(int state) { std::cout << "Update id=" << id_ << ", state=" << state << std::endl; }
private:
int id_;
std::weak_ptr<Foo> foo_;
};
int main() {
auto foo = std::make_shared<Foo>();
auto ob1 = FooObserver::create(101, foo);
{
auto ob2 = FooObserver::create(102, foo);
foo->notify(1);
}
foo->notify(2);
}
// 程序输出如下:
// Foo::Foo
// FooObserver::FooObserver() Update id=101,foo_.use_count=3
// FooObserver::FooObserver() Update id=102,foo_.use_count=3
// Update id=101, state=1
// Update id=102, state=1
// FooObserver::~FooObserver id=102, foo_.use_count=1
// Update id=101, state=2
// FooObserver::~FooObserver id=101, foo_.use_count=1
// Foo::~Foo observers_.size=0
实现
weak_ptr 和 shared_ptr 的实现相似,内部包含两个成员变量
cpp
template <typename _Tp, _Lock_policy _Lp = __default_lock_policy>
class __weak_ptr {
element_type* _M_ptr; // Contained pointer.
__weak_count<_Lp> _M_refcount; // Reference counter.
};
__weak_count 和 __shared_count 类似,实现了计数操作和保存对象指针( shared_count 是申请内存及构造对象),两者都包含同类型指针。
cpp
template <_Lock_policy _Lp = __default_lock_policy> class
__weak_count {
_Sp_counted_base<_Lp>* _M_pi;
};
这里只关注 weak_ptr 和 shared_ptr 的关联的部分,其余细节部分忽略.
基于 shared_ptr 构造 weak_ptr
就是将复制一下指针,弱引用计数+1
cpp
__weak_count(const __shared_count<_Lp>& __r) noexcept
: _M_pi(__r._M_pi)
{
if (_M_pi != nullptr)
_M_pi->_M_weak_add_ref();
}
从 weak_ptr 转换到 shared_ptr
weak_ptr 的 lock 实现如下,还是对指针的一个复制( _M_ptr 和 _M_pi )
cpp
// weak_ptr::lock
shared_ptr<_Tp> lock() const noexcept { return shared_ptr<_Tp>(*this, std::nothrow); }
// This constructor is non-standard, it is used by weak_ptr::lock().
shared_ptr(const weak_ptr<_Tp> &__r, std::nothrow_t) noexcept
: __shared_ptr<_Tp>(__r, std::nothrow) {}
// This constructor is used by __weak_ptr::lock() and
// shared_ptr::shared_ptr(const weak_ptr&, std::nothrow_t).
__shared_ptr(const __weak_ptr<_Tp, _Lp> &__r, std::nothrow_t) noexcept
: _M_refcount(__r._M_refcount, std::nothrow) {
_M_ptr = _M_refcount._M_get_use_count() ? __r._M_ptr : nullptr;
}
// Now that __weak_count is defined we can define this constructor:
template <_Lock_policy _Lp>
inline __shared_count<_Lp>::__shared_count(const __weak_count<_Lp> &__r, std::nothrow_t) noexcept
: _M_pi(__r._M_pi) {
if (_M_pi && !_M_pi->_M_add_ref_lock_nothrow())
_M_pi = nullptr;
}
enable_shared_from_this
可以通过调用成员函数 shared_from_this 让一个对象生成一个 shared_ptr 实例,进行共享所有权,
先看一下 enable_shared_from_this 结构,内部包含了一个 weak_ptr ,上面已经了解其用法: 作为观察者,必要的时候转换为 shared_ptr 共享所有权 。
cpp
template <typename _Tp> class enable_shared_from_this {
mutable weak_ptr<_Tp> _M_weak_this;
};
shared_from_this
那么何时转换为 shared_ptr ? 答:shared_from_this 。其实现为
cpp
shared_ptr<_Tp>
shared_from_this()
{ return shared_ptr<_Tp>(this->_M_weak_this); }
构造 shared_ptr 的过程和 weak_ptr::lock 相似,但是 lock() 不抛异常,shared_from_this 的构造是会抛异常的。
因为 lock 惯用在一些判断的逻辑内,而 shared_from_this 一般是作为一个有效所有权的实例参数传递,在后续的处理中再进行判断过于繁琐,直接抛异常更直观一些。
cpp
// Now that __weak_count is defined we can define this constructor:
template <_Lock_policy _Lp>
inline __shared_count<_Lp>::__shared_count(const __weak_count<_Lp> &__r) : _M_pi(__r._M_pi) {
if (_M_pi == nullptr || !_M_pi->_M_add_ref_lock_nothrow())
__throw_bad_weak_ptr();
}
标准规定 只容许在先前已由 std::shared_ptr 管理的对象上调用 shared_from_this,如何实现?
__shared_ptr 在构造的最后一步构造可能存在的 enable_shared_from_this
cpp
template <typename _Alloc, typename... _Args>
__shared_ptr(_Sp_alloc_shared_tag<_Alloc> __tag, _Args &&...__args)
: _M_ptr(), _M_refcount(_M_ptr, __tag, std::forward<_Args>(__args)...) {
_M_enable_shared_from_this_with(_M_ptr);
}
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);
}
template <typename _Tp1>
void _M_weak_assign(_Tp1 *__p, const __shared_count<_Lp> &__n) const noexcept {
_M_weak_this._M_assign(__p, __n);
}
// Used by __enable_shared_from_this.
void _M_assign(_Tp *__ptr, const __shared_count<_Lp> &__refcount) noexcept {
if (use_count() == 0) {
_M_ptr = __ptr;
_M_refcount = __refcount;
}
}
std::shared_ptr 持有的对象 T 进行构造发生在 _M_refcount 内,还没有到达 _M_enable_shared_from_this_with 处。
所以如果在构造函数内调用 shared_from_this 时,enable_shared_from_this::_M_weak_this 引用的对象为空,会抛出异常。
这就是为何上面观察者模式的 demo 使用了一个工厂函数 create 的原因.
同样的,在析构函数函数内也不能调用 shared_from_this,在析构函数调用之前,引用计数已经被置为 0
cpp
template <>
inline void _Sp_counted_base<_S_atomic>::_M_release() noexcept {
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
// ...
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1) {
_M_release_last_use(); // 内部调用析构函数
}
}
智能指针内存占用
unique_ptr 分配的内存为对象本身的大小,本身占用 8 个字节。
shared_ptr 占用情况为 16 字节( _Sp_counted_base 的两个变量加虚表)+ 对象对齐占用内存,比如有一个对象为
cpp
struct Fd {
int32_t fd;
};
sizeof(Fd) == 4;
那么 std::shared_ptr 的分配内存为 24 字节,智能指针本身内存为 8 个字节。
weak_ptr 不涉及内存分配,占用 16 个字节。
benchmark
对智能指针的 operator*
进行 benchmark,以普通指针作为参考,测试代码如下
cpp
// 防止进行编译期优化
static uint64_t normal = reinterpret_cast<uint64_t>(&normal);
static uint64_t unique = reinterpret_cast<uint64_t>(&unique);
static uint64_t shared = reinterpret_cast<uint64_t>(&shared);
static void BM_normal_ptr(benchmark::State &state) {
auto x = new uint64_t(1);
for (auto _ : state) {
*x += *x + 1;
normal += *x;
}
}
static void BM_unique_ptr(benchmark::State &state) {
auto x = std::make_unique<uint64_t>(1);
for (auto _ : state) {
*x += *x + 1;
unique += *x;
}
}
static void BM_shared_ptr(benchmark::State &state) {
auto x = std::make_shared<uint64_t>(1);
for (auto _ : state) {
*x += *x + 1;
shared += *x;
}
}
结果如下:
txt
// 优化等级 -O0
--------------------------------------------------------
Benchmark Time CPU Iterations
--------------------------------------------------------
BM_normal_ptr 1.24 ns 1.24 ns 562874831
BM_unique_ptr 27.7 ns 27.7 ns 25264819
BM_shared_ptr 9.35 ns 9.35 ns 75253037
// 优化等级 -O2
--------------------------------------------------------
Benchmark Time CPU Iterations
--------------------------------------------------------
BM_normal_ptr 0.219 ns 0.219 ns 3157103856
BM_unique_ptr 0.217 ns 0.217 ns 3182580916
BM_shared_ptr 0.217 ns 0.217 ns 3206275755
挺意外的一个点是 unique_ptr 在没有优化的情况下居然是最耗时间的,不过在 -O2 的优化等级下,三个指针的访问速度是同一个级别的。
总结
- 使用智能指针可以减少代码的行数,加强所有权语义
- unique_ptr 不支持复制的动作,适用无共享所有权的地方
- shared_ptr 适用于需要共享所有权的地方,并且智能指针本身的操作是线程安全的,但是不保证对象操作是线程安全的。
- weak_ptr 适用于观察者的角色,作为辅助 shared_ptr 而存在,可以切断循环引用。
- enable_shared_from_this 适用在对象内部转换一个新的 shared_ptr,比如在 asio 中作为 tcp 连接读写函数的参数
- 内存占用 shared_ptr 相比 unique_ptr 多出 16+ 字节分配。
- benchmark 开启 -O2 的优化等级后,三类指针的解指针访问速度是同一个等级的。