1 Preface

软件工程遇到的问题都可以通过增加一个中间层来解决, 智能指针也是基于这样的思想;

C++ 11 包含了以下 3 种常用的智能指针:

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

包含头文件 #include <memory> 即可

2 原理分析

2.1 shared_ptr

直接用 shared_ptr 管理一个堆上的裸指针对象:

1
std::shared_ptr<Good> gp1(new Good());

shared_ptr

2.2 weak_ptr

weak_ptr类 和 shared_ptr类 的成员变量相同。

weak_ptr

为什么需要 weak_ptr?
因为 shared_ptr 是对象的强引用, 一旦发生循环引用,对象就无法析构, 所以 weak_ptr 出现就是为了解决循环引用的问题。
打个不恰当的比喻:weak_ptr就像寄生虫,shared_ptr就是宿主。

weak_ptr 和 shared_ptr 内存结构相同(1 个原始对象指针 + ctrl_block_t 指针);

不同之处在于:

(1) 不直接使用 weak_ptr 进行对象的各种操作
每次使用 weak_ptr 时,提升一个shared_ptr, 然后使用shared_ptr; 但是如果提升失败,表示对象已经析构。

(2) 负责管理不同对象的生命周期
shared_ptr 管理 T*对象的生命周期,而 weak_ptr 管理 ctrl_block_t* 的生命周期。

(3) 不对称
shared_ptr 可以构造对象产生(make_shared() 或 shared_ptr(T*)),或者通过 weak_ptr 提升。

1
2
weak_ptr<T> wp;
shared_ptr<T> sp = wp.lock(); //提升

weak_ptr 通过拷贝 shared_ptr 构造得到,或者直接拷贝(或移动构造)其他的 weak_ptr
也就是说, 第一个有效的weak_ptr 一定从 shared_ptr 拷贝得到的。

1
2
3
4
shared_ptr<T> sp;
weak_ptr<T> wp = sp;  // 拷贝 shared_ptr 构造
weak_ptr<T> wp1 = wp; // 拷贝 weak_ptr 构造 
weak_ptr<T> wp2 = std::move(wp1); //weak_ptr move构造

2.3 make_shared<T>()

为什么要引入make_shared?

好处1: 把指针计数器和对象一次性一起分配;提高内存分配效率。
好处2: 提高数据的局部性,引用计数和对象可能在同一内存页中(甚至是 same cache line)。

所以, 一般来说, 我们推荐使用 make_shared 来创建shared_ptr对象, 能够有效的减少一次堆的小对象分配。

1
std::shared_ptr<Good> gp1 = std::make_shared<Good>();

make_shared

2.4 enable_shared_from_this

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Good : public std::enable_shared_from_this<Good> // public 继承
{
public:
    std::shared_ptr<Good> getInstance() {
        return shared_from_this();
    }
    ~Good() { std::cout << "Good::~Good() called" << std::endl; }
};

// Good: the two shared_ptr's share the same object
std::shared_ptr<Good> gp1 = std::make_shared<Good>();
std::shared_ptr<Good> gp2 = gp1->getInstance();
std::cout << "gp2.use_count() = " << gp2.use_count() << '\n'; // output 2

c++ 为了解决class内部, 使用自身class的智能指针的问题,引入了enable_shared_from_this,
enable_shared_from_this 这个类只包含一个 weak_ptr<T> 成员对象(16 bytes 2个指针),
当每次调用shared_from_this(), 就通过 weak_ptr 提升创建一个 shared_ptr 对象。

其内存模型如下:

enable_shared_from_this

2.5 unique_ptr

unique_ptr<> 管理一个独占的指针对象;

unique_ptr_internal

2.6 make_unique<T>()

make_unique<> 只是 unique_ptr<T>(new T()) 语法糖的包装, 作用只是为了智能指都有make_xxx一个统一的写法而已。

3 对象字节数(64 bit system)

a> shared_ptr, weak_ptr 由于内部都只包含两个pointer,所以都是 16 bytes;

b> control_block<T*> 包含两个 atomic_llong 和一个指针,所以是 24 bytes;

c> 特别地,如果采用 make_shared 来分配智能指针,不考虑内存对其的前提下,
control_block<T*> 占用空间为 16 + sizeof(T)

d> unique_ptr 内部有一个 tuple<T*,functor_class> 成员, 这个tuple成员对象只占用 8 bytes ;

4 shared_ptr 和 weak_ptr 实现的细节

  • 当每次有新的 shared_ptr 生成时,会增加 _Sp_counted_base 的 _M_use_count (+1);

  • 当每次有新的 weak_ptr 生成时,会增加 _Sp_counted_base 的 _M_weak_count (+1);

  • 通过weak_ptr<x>::lock()函数可以获取x对象的强引用(shared_ptr)。
    通过判断 !expired(){return _M_use_count == 0;}, 那么可以提升成功。

  • 对应的,shared_ptr 析构时,会调用 _Sp_counted_base 的 _M_release 来使 _M_use_count (-1);

  • weak_ptr 析构时,会调用 _Sp_counted_base 的 _M_release 来使 _M_weak_count (-1);

  • 当 _M_use_count 的数量为 0 的时候 就会释放,原始的对象(用户自定义对象) 的内存,但是不会释放 _Sp_counted_base 的内存。

  • 当 _M_use_count 和 _M_weak_count 都为 0 的时候, 才会释放 _Sp_counted_base 的内存。

5 最后

最后,我们来看下智能指针内存空间分布:

  1. 智能指针对象自身是在当前的栈空间上,而智能指针管理的对象是在堆空间!
  2. 特别的,对于 shared_ptr 还多一个控制块对象,这个控制块也在堆空间上。

6 答疑

❓* 深挖 unique_ptr 的 tuple 成员对象为啥只占用8个字节?*

因为 gcc 内部实现tuple采用的是递归继承的方式,对应的继承树为:

tuple<T*,class functor()> -> tuple<class functor()> -> tuple<>

其中仿函数的缺省实现(简化版):

void operator()(Tp* ptr) const { delete ptr; }

所以问题的本质是:继承一个没有任何成员的 class, 并不会增加类的大小!

make_shared 对象的内存可能无法及时回收?

make_shared 只分配一次内存, 这看起来很好. 减少了内存分配的开销。 问题来了, weak_ptr 会保持控制块(强引用, 以及弱引用的信息)的生命周期, 而因此连带着保持了对象分配的内存, 只有最后一个 weak_ptr 离开作用域时, 内存才会被释放. 原本强引用减为 0 时就可以释放的内存, 现在变为了强引用, 弱引用都减为 0 时才能释放, 意外的延迟了内存释放的时间. 这对于内存要求高的场景来说, 是一个需要注意的问题. 关于这个问题可以看这里 make_shared, almost a silver bullet

附录

测试代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#pragma pack(1)
#include <memory>
#include <iostream>

int main()
{
    std::shared_ptr<std::string> sp = std::make_shared<std::string>("123");
    std::cout << "\n sizeof  std::shared_ptr<string> " << sizeof(sp);
    std::cout << "\n sizeof(int) " << sizeof(int) <<  ", sizeof(string) " << sizeof(std::string);
    std::cout << "\n sizeof(_Atomic_word) " << sizeof(_Atomic_word);

    sp.get();
    std::cout << "\n size std::__shared_count(8) = "
        << sizeof(std::__shared_count<std::__default_lock_policy>(sp.get()));

    typedef std::_Sp_counted_ptr<std::string*,std::__default_lock_policy> counter_ptr;

    std::cout << "\n counter_ptr size :" << sizeof (counter_ptr);

    typedef std::_Sp_counted_ptr_inplace<int, std::allocator<int>, std::__default_lock_policy> counter_in_place;
    std::cout << "\n counter_in_place :" << sizeof (counter_in_place);

    typedef std::_Sp_counted_ptr_inplace<char, std::allocator<char>, std::__default_lock_policy> counter_in_place_char;
    std::cout << "\n counter_in_place_char :" << sizeof (counter_in_place_char);

    typedef std::enable_shared_from_this<std::string> from_this;
    std::cout << "\n from_this (equals weak_ptr<T>):" << sizeof (from_this);

    typedef  std::weak_ptr<std::string> weak_ptr_tp;
    std::cout << "\n weak_ptr_tp :" << sizeof (weak_ptr_tp);

    std::unique_ptr<std::string> up = std::make_unique<std::string>("hello");
    std::cout << "\n sizeof(up) :" << sizeof (up); // 8 bytes

    std::__uniq_ptr_impl<std::string, std::default_delete<std::string>> unique_imp;

    std::cout << "\n sizeof(unique_imp) :" << sizeof (unique_imp); // 8 bytes

    std::tuple<std::string*, std::default_delete<std::string>> tup;
    std::cout << "\n sizeof(tuple<pointer, deleter()>) :" << sizeof (tup); // 8 bytes
    std::cout << "\n";
    return  0;
}
#pragma pack()

输出结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/**
 * # g++ -g -Wall main.cpp -o a.out
 * # ./a.out
sizeof  std::shared_ptr<string> 16
sizeof(int) 4, sizeof(string) 8
sizeof(_Atomic_word) 4
size std::__shared_count(8) = 8
 counter_ptr size :24
 counter_in_place :20
 counter_in_place_char :17
 from_this (equals weak_ptr<T>):16
 weak_ptr_tp :16
 sizeof(up) :8
 sizeof(unique_imp) :8
 sizeof(tuple<pointer, deleter()>) :8
*/

last update: 2020-08-04 17:30:04 by sunquan