[C++] 智能指针一些实现分析

智能指针一些实现分析

提供值传递但是指针语义的功能。通过指针占用并且对管理对象,在离开作用域时释放该对象。

在使用上还有另外一个很好用的功能,精简了代码复杂度,管理的对象类可以省略以下的函数

  1. 默认构造函数
  2. 复制构造函数
  3. 复制赋值函数

比如有一个类 Fd 用于管理 fd ,并且拥有 fd 的所有权,所以 Fd 一定需要包含一个 fd 实体及不应该被复制。
所以需要实现三个函数

  1. 普通的 fd 构造函数
  2. 移动构造函数
  3. 析构函数
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 的情况下,编译是不通过的。

std::map<std::string, Fd> fdmap;
auto x = fdmap["x"];

未实现默认构造函数的报错如下:

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))...)
      |         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

在实现了 移动构造函数 但是未实现 复制构造函数 的情况下,默认不会生成 复制构造函数 ,报错如下:

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

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

也能同样引用在多态的场景

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 不支持复制操作,语义上是唯一的(符合命名)。

一些其他的特性支持

  1. 自定义删除器
  2. 移动语义

在细看 unique_ptr 的实现之前,先看一下 auto_ptr 及其被废弃的原因。

std::auto_ptr(C++17废弃)

std::auto_ptr 是最早的智能指针,对于所有权的管理比较原始,和语义的基础设施也有点关系。

被废弃的最大原因,auto_ptr 支持赋值操作,旧的指针被置为 0(有点类似移动语义)。

代码的实现如下:

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 还能够继续使用,没有限制,非常容易造成问题。

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 将复制的动作删除

// Disable copy from lvalue.
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;

在析构函数内通过删除器对对象进行删除,这样就可以自定义删除了。

~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) { }

explicit operator bool() const noexcept {
    return get() == pointer() ? false : true;
}

支持移动语义

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 可能使用受限

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

__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,如果为最后一个计数,那么就进行释放。

if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
    _M_release_last_use();

实现

通过 gdb 跟踪一下 std::make_shared 的调用链,停在调用 enable_from_this 的地方。

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 .

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 的 *-> 操作。一般情况下使用的类型为

__shared_ptr_access<int, (__gnu_cxx::_Lock_policy)2, false, false>

实现为通过继承的方式,在父类中调用子类的成员函数。

// 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 指针类型.

template<_Lock_policy _Lp>
class __shared_count {
  private:
    _Sp_counted_base<_Lp>*  _M_pi;
}
_Sp_counted_base

内部定义了基本的引用计数的操作成员函数,和对象释放的虚函数接口。

包含两个变量,加上虚表 _Sp_counted_base 的大小为 16 个字节。

_Atomic_word  _M_use_count;     // #shared
_Atomic_word  _M_weak_count;    // #weak + (#shared != 0)
_Sp_counted_ptr_inplace

继承了 _Sp_counted_base ,对象的内存申请和构造在这个类中实现

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

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 的实现

构造函数 内构造对象(及指针)和引用计数。

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

~__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 了

复制构造函数 内增加引用计数

__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) 进行特化实现了三套接口,对引用计数的操作保证线程安全,但是后续取出来的指针的线程安全需要用户去保证。

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,需要复制对象内存指针和 移动 删除器,最终实现为

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 导致资源未释放

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,那么引用计数不会增加,将打破循环引用。
ab 的所有权属于 main 函数,而内部的 ab 属于一个观察者的角色。

一个经典的观察者模式的 demo 代码,演示了如何使用 weak_ptr 扮演一个观察者的角色,此刻的 shared_ptr 的所有权只属于 main 函数。

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_ptrshared_ptr 的实现相似,内部包含两个成员变量

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 是申请内存及构造对象),两者都包含同类型指针。

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

__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

// 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 共享所有权

template <typename _Tp> class enable_shared_from_this {
    mutable weak_ptr<_Tp>  _M_weak_this;
};

shared_from_this

那么何时转换为 shared_ptr ? 答:shared_from_this 。其实现为

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 一般是作为一个有效所有权的实例参数传递,在后续的处理中再进行判断过于繁琐,直接抛异常更直观一些。

// 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

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

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 的两个变量加虚表)+ 对象对齐占用内存,比如有一个对象为

struct Fd {
    int32_t fd;
};

sizeof(Fd) == 4;

那么 std::shared_ptr 的分配内存为 24 字节,智能指针本身内存为 8 个字节。

weak_ptr 不涉及内存分配,占用 16 个字节。

benchmark

对智能指针的 operator* 进行 benchmark,以普通指针作为参考,测试代码如下

// 防止进行编译期优化
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;
    }
}

结果如下:

// 优化等级 -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 的优化等级下,三个指针的访问速度是同一个级别的。

总结

  1. 使用智能指针可以减少代码的行数,加强所有权语义
  2. unique_ptr 不支持复制的动作,适用无共享所有权的地方
  3. shared_ptr 适用于需要共享所有权的地方,并且智能指针本身的操作是线程安全的,但是不保证对象操作是线程安全的。
    1. weak_ptr 适用于观察者的角色,作为辅助 shared_ptr 而存在,可以切断循环引用。
    2. enable_shared_from_this 适用在对象内部转换一个新的 shared_ptr,比如在 asio 中作为 tcp 连接读写函数的参数
  4. 内存占用 shared_ptr 相比 unique_ptr 多出 16+ 字节分配。
  5. benchmark 开启 -O2 的优化等级后,三类指针的解指针访问速度是同一个等级的。

posted on 2020-01-27 21:57  文一路挖坑侠  阅读(745)  评论(0编辑  收藏  举报

导航