(C++) C++ 中 shared_ptr weak_ptr

2023-02-15,

shared_ptr

std::shared_ptr<int> sp1 = new int();  // shared count = 1, weak count = 0
std::shared_ptr<int> sp2(sp1); // shared count = 2, weak count = 0
std::shared_ptr<int> sp3 = sp2; // shared count = 3, weak count = 0 std::weak_ptr<int> wp1(sp1); // shared count = 3, weak count = 1
std::weak_ptr<int> wp2(wp1); // shared count = 3, weak count = 2
std::weak_ptr<int> wp3 = wp2; // shared count = 3, weak count = 3

shared_ptr weak_ptr 使用 reset 或者指向另一个 managed object导致 shared count或weak count相应的减一。

    类继承中使用shared_ptr
class Base {};
class Derived : public Base {};
......
shared_ptr<Derived> dp1(new Derived);
shared_ptr<Base> bp1 = dp1;
shared_ptr<Base> bp2(dp1);
shared_ptr<Base> bp3(new Derived);
    casting shared_ptr
shared_ptr<Base> base_ptr (new Base);
shared_ptr<Derived> derived_ptr;
// if static_cast<Derived *>(base_ptr.get()) is valid, then the following is valid:
derived_ptr = static_pointer_cast<Derived>(base_ptr);
    make_shared

使用shared_ptr = new int(),会导致两次内存分配:int对象的内存分配跟shared_ptr内部的 manager object 一次内存分配。make_shared 对此进行了优化,一次性分配 int + manager object 内存空间大小。

make_shared 用法:

shared_ptr<Thing> p (make_shared<Thing>(42, "I'm a Thing!"));
shared_ptr<Base> bp(make_shared<Derived1>()); // shared_ptr中的 template参数与make_shared中的tmeplate参数可以不一样(继承关系)

使用 weak_ptr

void do_it(weak_ptr<Thing> wp){
shared_ptr<Thing> sp = wp.lock(); // get shared_ptr from weak_ptr
if(sp)
sp->defrangulate(); // tell the Thing to do something
else
cout << "The Thing is gone!" << endl;
}

也可以直接从weak_ptr构建shared_ptr,这个时间如果weak_ptr过期(通过 weak_ptr::expired() 可以查询),则抛出异常:

void do_it(weak_ptr<Thing> wp){
shared_ptr<Thing> sp(wp); // construct shared_ptr from weak_ptr
// exception thrown if wp is expired, so if here, sp is good to go
sp->defrangulate(); // tell the Thing to do something
}

shared_ptr 线程安全

shared_ptr / weak_ptr 中的引用计数是线程安全的(atomic/memory barrier);但是跨线程赋值操作、reset操作没有线程安全。即

    shared_ptr 仅保证引用计数的正确性,在引用计数减为0之后,析构掉被管理对象;
    在当前线程中进行赋值操作。不应在其他线程中进行赋值、reset操作;

enable_shared_from_this 及 shared_from_this

定义一个类,实现内存自动管理。两次调用 get 返回的是两个不同的shared_ptr,将导致同一个BadThing对象两次析构。

class BadThing {
public:
BadThing() = default;
shared_ptr<BadThing> get() { return std::make_shared<BadThing>(); }
};
    enable_shared_from_this的实现
// enable_shared_from_this的实现
// 基于(/usr/include/c++/7.3.0/bits/shared_ptr.h)
// 此代码是对gcc实现的简化版本, 仅作为描述原理用.
template<typename T>
class enable_shared_from_this
{
public:
shared_ptr<T> shared_from_this()
{
return shared_ptr<T>(this->weak_this);
}
shared_ptr<const T> shared_from_this() const
{
return shared_ptr<const T>(this->weak_this);
}
private:
template<typename>
friend class shared_ptr; template<typename T1>
void _M_weak_assign(T1* p, const shared_count<>& n)
{
  weak_this._M_assign(p, n);
} mutable weak_ptr<T> weak_this;
};

enable_shared_from_this 类中定义了一个 weak_ptr, 起到了上文提到的从obj指针生成 shared_ptr 对象的作用. 按照先前的原理, 我们可能认为是在obj初始化的时候, 同时对 weak_this 进行初始化, 但是在这段代码里显然没有对 weak_this 进行任何初始化工作(原始代码里也没有, gcc为什么不这样实现呢? 这是因为当对象没有由智能指针管理时, 这些操作是没有必要的. 所以应该把这个任务交给 shared_ptr). 第一个持有 Good 对象 g_obj 的 shared_ptr sp1 会对 g_obj 的 weak_this 进行处理, 使其有效. 具体见参考。

为什么

    为什么需要 weak_ptr:编译期间解除A/B循环引用导致两者都不能释放内存,使用weak_ptr作为对方的引用 ref;
    为什么需要 enable_shared_from_this:特别的,在类内部需要调用函数(如创建线程等异步处理将自身作为参数);

参考

Using C++11’s Smart Pointers PDF

enable_shared_from_this类的作用和实现

std::shared_ptr std::weak_ptr 线程安全性

从 shared_from_this() 谈智能指针 weak_ptr 和 shared_ptr 的实现

(C++) C++ 中 shared_ptr weak_ptr的相关教程结束。

《(C++) C++ 中 shared_ptr weak_ptr.doc》

下载本文的Word格式文档,以方便收藏与打印。