首页 > 其他分享 >智能指针一些实现分析

智能指针一些实现分析

时间:2024-05-28 18:11:36浏览次数:25  
标签:分析 __ weak count 智能 std shared ptr 指针

智能指针一些实现分析

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

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

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

标签:分析,__,weak,count,智能,std,shared,ptr,指针
From: https://www.cnblogs.com/shuqin/p/18218600

相关文章

  • “事后诸葛亮”分析
    “事后诸葛亮”分析1.会议照片:出于最近课程作业较多成员较为忙碌,改为微信群聊讨论的会议方式2.会议分析内容:设想和目标我们的软件要解决什么问题?为广大热爱网购或者只能通过网购这种渠道获取自己想要的商品的群体,以及需要售货渠道的商家,提供一个线上的购物商城;......
  • 如何实时掌握手机号状态的API利器分析
    在移动互联网的时代,手机号码不仅是通信的连接点,也是用户身份的关键识别。手机状态查询API通过提供实时的手机号码状态查询服务,协助企业和组织更有效地管理用户信息,提升服务流程。手机状态查询API通过与电信运营商的数据库进行请求,从而获取手机号码的当前状态。用户可通过以......
  • 基于Python实现可视化分析中国500强排行榜数据的设计与实现
    基于Python实现可视化分析中国500强排行榜数据的设计与实现“DesignandImplementationofVisualAnalysisforChina’sTop500CompaniesRankingDatausingPython”完整下载链接:基于Python实现可视化分析中国500强排行榜数据的设计与实现文章目录基于Python......
  • Hadoop HDFS NameNode核心原理分析
    胡弦,视频号2023年度优秀创作者,互联网大厂P8技术专家,SpringCloudAlibaba微服务架构实战派(上下册)和RocketMQ消息中间件实战派(上下册)的作者,资深架构师,技术负责人,极客时间训练营讲师,四维口袋KVP最具价值技术专家,技术领域专家团成员,2021电子工业出版社年度优秀作者,获得2023电......
  • 软文外链的持久性分析:如何保证链接的长效性
    软文外链的持久性分析:如何保证链接的长效性在当今数字化时代,网络营销已经成为企业推广和品牌建设的重要手段。而软文外链作为一种常见的网络营销方式,对于提升网站权重和流量起着至关重要的作用。然而,许多企业在进行软文外链推广时往往忽略了链接的持久性问题,导致投入产出不成正比......
  • 事后诸葛亮分析报告
    作业所属班级https://edu.cnblogs.com/campus/gdgy/SoftwareEngineering2024作业要求https://edu.cnblogs.com/campus/gdgy/SoftwareEngineering2024/homework/13143作业目标召开事后诸葛亮会议,发布一篇事后分析报告一、设想和目标我们的软件要解决什么问题?是......
  • 亚马逊云主管马特·加尔曼面临压力,致力于在人工智能领域赶超竞争对手
      每周跟踪AI热点新闻动向和震撼发展想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领域的领跑者。点击订阅,与未来同行!订阅:https://......
  • 事后诸葛亮分析
    作业所属课程软工2024作业要求自我介绍+软工5问作业目标学习使用一些好用实用的工具。熟悉作业提交的方法和格式。督促我翻阅课本。明确自己的学习方向该软件的设计目标是让用户清晰地了解近几日的天气预报。其基本功能都已实现。由于基本上是个人项目,所以并......
  • 基于STM32的智能个人健康助手
    设计摘要:基于单片机的智能个人健康助手是一种集成了传感器、数据处理和智能算法的智能设备,旨在帮助用户监测和改善个人健康状况。该助手可以通过采集用户的体温、心率和血氧等,进行数据分析和健康评估,并提供个性化的健康建议和提醒。该系统的核心部件是单片机,它可以连接各种传......
  • 基于单片机的智能药盒
    设计摘要:基于单片机的智能药盒系统是一种集成了电子技术和智能化管理的药物管理系统。该系统通过使用单片机作为核心控制器,结合传感器、显示器、报警器等元件,实现对药物的存储、提醒和监控等功能。通过预先设定的时间和剂量,系统可以准确地提醒患者按时服药,并记录药物的使用情......