0%

面试知识点详细解读之智能指针

关于智能指针的demo,参见C++ primer 12.1

什么是智能指针

智能指针是使用RAII手法对裸指针进行的一个面向对象封装,即在构造函数中初始化资源地址,在析构函数中释放资源。智能指针保证当资源应当被释放的时候一定会释放它,这是利用了栈上的对象出作用域时自动析构这个特点。

为什么引入智能指针

用以弥补裸指针的不足:

​ 1)使用裸指针分配内存后,没有对指针释放资源会导致内存泄漏;

​ 2)多个裸指针指向同一资源时,多次释放资源时,对空悬指针进行释放会导致不可预知的错误。

如何知道是否存在内存泄漏

在不借助其他检测工具的情况下,可以通过观察,即程序长时间运行后内存占用率一直不断的缓慢的上升,而实际上在你的逻辑中并没有这么多的内存需求。

如何定位到内存泄漏点
  • review代码,找到newdelete关键字的位置,先看看内存的申请和释放是否是成对的来进行初步的判断;
  • 对于函数中申请的临时空间,认真检查是否存在提前跳出函数的地方而导致没有释放内存。
智能指针有哪些

智能指针分为不带引用计数的scoped_ptrunique_ptr,带引用计数的shared_ptrweak_ptr

这些智能指针分别是如何实现的

scoped_ptr私有化了拷贝构造函数和赋值操作运算符,资源的所有权无法进行转移,也无法在容器中使用,这种方式杜绝了浅拷贝的发生。

unique_ptr删除了拷贝构造函数和赋值函数,因此不支持普通的拷贝或赋值操作。但引入了移动构造函数和移动赋值运算符。所以它们保证了有唯一的智能指针持有此资源。unique_ptr还提供了reset重置资源,swap交换资源等函数,也经常会使用到。

shared_ptr称为强智能指针,它的资源引用计数器在内存的heap堆上(这保证了,每个智能指针的引用计数变量会动态的变化)。通常用于管理对象的生命周期。只要有一个指向对象的shared_ptr存在,该对象就不会被析构。

weak_ptr被称为弱智能指针,其对资源的引用不会引起资源的引用计数的变化,通常作为观察者,用于判断资源是否存在,并根据不同情况做出相应的操作。比如使用weak_ptr对资源进行弱引用,当调用weak_ptrlock()方法时,若返回nullptr,则说明资源已经不存在,放弃对资源继续操作。否则,将返回一个shared_ptr对象,可以继续操作资源。另外,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。

当需要多个智能指针指向同一个资源时,使用带引用计数的智能指针。每增加一个智能指针指向同一资源,资源引用计数加1,反之减1。当引用计数为0时,由最后一个指向资源的智能指针将资源进行释放。

如何避免循环引用
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
#include <iostream>
#include <memory>
using namespace std;

class B; // 前置声明类B
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
shared_ptr<B> _ptrb; // 指向B对象的智能指针
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
shared_ptr<A> _ptra; // 指向A对象的智能指针
};
int main()
{
shared_ptr<A> ptra(new A()); // ptra指向A对象,A的引用计数为1
shared_ptr<B> ptrb(new B()); // ptrb指向B对象,B的引用计数为1
ptra->_ptrb = ptrb; // A对象的成员变量_ptrb也指向B对象,B的引用计数为2
ptrb->_ptra = ptra; // B对象的成员变量_ptra也指向A对象,A的引用计数为2

cout << ptra.use_count() << endl; // 打印A的引用计数结果:2
cout << ptrb.use_count() << endl; // 打印B的引用计数结果:2

/*
出main函数作用域,ptra和ptrb两个局部对象析构,分别给A对象和
B对象的引用计数从2减到1,达不到释放A和B的条件(释放的条件是
A和B的引用计数为0),因此造成两个new出来的A和B对象无法释放,
导致内存泄露,这个问题就是“强智能指针的交叉引用(循环引用)问题”
*/
return 0;
}

解决办法,这也是强弱智能指针的一个重要应用规则:定义对象时,用强智能指针shared_ptr,在其它地方引用对象时,使用弱智能指针weak_ptr

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
#include <iostream>
#include <memory>
using namespace std;

class B; // 前置声明类 B
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
weak_ptr<B> _ptrb; // 指向 B 对象的弱智能指针。引用对象时,用弱智能指针
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
weak_ptr<A> _ptra; // 指向 A 对象的弱智能指针。引用对象时,用弱智能指针
};

int main()
{
// 定义对象时,用强智能指针
shared_ptr<A> ptra(new A()); // ptra 指向 A 对象,A 的引用计数为 1
shared_ptr<B> ptrb(new B()); // ptrb 指向B 对象,B 的引用计数为 1
// A 对象的成员变量 ptrb 也指向 B 对象,B 的引用计数为 1,因为是弱智能指针,引用计数没有改变
ptra->_ptrb = ptrb;
// B 对象的成员变量 ptra 也指向 A 对象,A 的引用计数为 1,因为是弱智能指针,引用计数没有改变
ptrb->_ptra = ptra;

cout << ptra.use_count() << endl; // 打印结果: 1
cout << ptrb.use_count() << endl; // 打印结果: 1

/*
出 main 函数作用域,ptra 和 ptrb 两个局部对象析构,分别给 A 对象和
B 对象的引用计数从 1 减到 0,达到释放 A 和 B 的条件,因此 new 出来的 A 和 B 对象
被析构掉,解决了“强智能指针的交叉引用(循环引用)问题”
*/
return 0;
}
什么情况下需要自定义deleter

在管理的裸指针不是new出来的裸指针时需要自定义删除器,比如管理的指针为文件指针malloc获得的指针等。

enable_shared_from_this机制

class Widgetshare_ptr管理,且在Widget的成员函数里需要把当前类对象的智能指针作为参数传给其他函数时,就需要传递一个指向自身的share_ptr

而当使用shared_ptr管理同一资源,调用shared_ptr的构造函数和拷贝构造函数是不一样的(构造函数新创建一份堆上的引用计数资源,拷贝构造函数修改原来堆上的引用计数资源),它们虽然使得不同shared_ptr指向同一资源,但管理引用计数资源的方式却不一样。这就造成了不能直接传递shared_ptr<Widget>(this)。因为这样会造成2个非共享的share_ptr指向同一个对象,引用计数资源相互独立导致对象被析构2次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <memory>
#include <iostream>

class Widget
{
public:
std::shared_ptr<Widget> getptr()
{
return std::shared_ptr<Widget>(this);
}
~Widget() { std::cout << "Widget::~Widget() called" << std::endl; }
};

int main()
{
// 错误的示例, 每个 shared_ptr 都认为自己是对象仅有的所有者
std::shared_ptr<Widget> p1(new Widget;
std::shared_ptr<Widget> p2 = p1->getptr();
// 打印 p1 和 p2 的引用计数
std::cout << "p1.use_count() = " << p1.use_count() << std::endl; // 输出为 1
std::cout << "p2.use_count() = " << p2.use_count() << std::endl; // 输出为 1
} // Widget 对象将会被删除两次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <memory>
#include <iostream>

struct Widget : public std::enable_shared_from_this<Widget> // 注意:继承
{
public:
std::shared_ptr<Widget> getptr()
{
return shared_from_this();
}
~Widget() { std::cout << "Widget::~Widget() called" << std::endl; }
};

int main()
{

std::shared_ptr<Widget> p1(new Widget;
std::shared_ptr<Widget> p2 = p1->getptr();
// 打印 p1 和 p2 的引用计数
std::cout << "p1.use_count() = " << p1.use_count() << std::endl; // 输出为 2
std::cout << "p2.use_count() = " << p2.use_count() << std::endl; // 输出为 2
} // // Widget 对象只会被删除一次

实际使用场景:在异步调用中,存在一个保活机制,异步函数执行的时间点我们是无法确定的,然而异步函数可能会使用到异步调用之前就存在的变量。为了保证该变量在异步函数执期间一直有效,我们可以传递一个指向自身的share_ptr给异步函数,这样在异步函数执行期间share_ptr所管理的对象就不会析构,所使用的变量也会一直有效了(保活)。

enable_shared_from_this是如何实现的

enable_shared_from_this类中包含一个作为观察者的成员变量,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template<class T>
class enable_shared_from_this
{
public:
...
SetSharedPtr(shared_ptr<T>* sp)
{
if(weak_this_.expired())
wp = sp; // 标准库中并没有把 sp 直接赋值给 wp,而是使用了 shared_ptr 的别名构造函数
}

__shared_ptr<_Tp, _Lp> shared_from_this()
{
return __shared_ptr<_Tp, _Lp>(this->_M_weak_this);
}

__shared_ptr<const _Tp, _Lp> shared_from_this() const
{
return __shared_ptr<const _Tp, _Lp>(this->_M_weak_this);
}
private:
mutable weak_ptr<_Tp> _M_weak_this; // 使用 weak_ptr 的目的正是为了避免循环引用!
};

当一个类继承了enable_shared_from_this类,就继承了_M_weak_this这个成员变量。

使用shared_ptr<Widget>(new Widget())第一次构造智能指针对象时,就会初始化一个作为观察者的弱智能指针_M_weak_this指向Widget对象资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class shared_ptr<T>
{
public:
explicit shared_ptr<T>(T* w)
{
SetSharedPtr(this, w);
}

void SetSharedPtr(shared_ptr<T>* sp, enable_shared_from_this<T>* w)
{
w->SetSharedPtr(sp);
}

// 此重载函数的作用是为了处理 Widget 并没有继承 enable_shared_from_this 的情况
void SetSharedPtr(...)
{
// 什么也不做
}
};

通过shared_from_this()方法代替shared_ptr的普通构造函数来返回一个shared_ptr对象,从而避免产生额外的引用计数资源对象。在shared_from_this()函数中,是通过_M_weak_this来构造并返回一个shared_ptr对象的。

智能指针源码分析

unique_ptr的声明包含两个模板参数,第一个参数_Tp显然就是原生指针的类型。第二个模板参数_Dp是一个deleter,默认值为default_delete<_Tp>default_delete是一个针对delete operator的函数对象。

unique_ptr内部用tuple<pointer, _Dp> _M_t;变量保存数据,相当于原生指针和deleter对象组成的一个pair

定义了构造函数和移动构造函数和移动赋值运算符,但是删除了拷贝构造函数和拷贝赋值运算符。

unqiue_ptr还定义了两个很重要的函数:reset(pointer)release()reset(pointer)的功能是用一个新指针替换原来的指针,而release()则是是放弃原生指针的所有权。

到目前为止,unique_ptr还不像个指针,因为还缺少两个方法:operator*operator->

下面是unique_ptr的源码,有删减,但大体上不变。

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
template <typename _Tp>
struct default_delete
{
/// Default constructor
constexpr default_delete() noexcept = default;

void operator()(_Tp *__ptr) const
{
delete __ptr;
}
};

template <typename _Tp, typename _Dp>
class __uniq_ptr_impl
{
template <typename _Up, typename _Ep, typename = void>
struct _Ptr
{
using type = _Up *;
};

template <typename _Up, typename _Ep>
struct _Ptr<_Up, _Ep, __void_t<typename remove_reference<_Ep>::type::pointer>>
{
using type = typename remove_reference<_Ep>::type::pointer;
};

public:
using pointer = typename _Ptr<_Tp, _Dp>::type;

__uniq_ptr_impl() = default;
__uniq_ptr_impl(pointer __p) : _M_t() { _M_ptr() = __p; }

template <typename _Del>
__uniq_ptr_impl(pointer __p, _Del &&__d) : _M_t(__p, std::forward<_Del>(__d)) {}

pointer &_M_ptr() { return std::get<0>(_M_t); }
pointer _M_ptr() const { return std::get<0>(_M_t); }
_Dp &_M_deleter() { return std::get<1>(_M_t); }
const _Dp &_M_deleter() const { return std::get<1>(_M_t); }

private:
tuple<pointer, _Dp> _M_t;
};

template <typename _Tp, typename _Dp = default_delete<_Tp>>
class unique_ptr
{
__uniq_ptr_impl<_Tp, _Dp> _M_t;

public:
using pointer = typename __uniq_ptr_impl<_Tp, _Dp>::pointer;
using element_type = _Tp;
using deleter_type = _Dp;

// Constructors.

/// Default constructor, creates a unique_ptr that owns nothing.
template <typename _Up = _Dp, typename = _DeleterConstraint<_Up>>
constexpr unique_ptr() noexcept : _M_t()
{
}

template <typename _Up = _Dp, typename = _DeleterConstraint<_Up>>
explicit unique_ptr(pointer __p) noexcept : _M_t(__p)
{
}

unique_ptr(pointer __p, typename remove_reference<deleter_type>::type &&__d) noexcept
: _M_t(std::move(__p), std::move(__d))
{
}

// Move constructors.

/// Move constructor.
unique_ptr(unique_ptr &&__u) noexcept
: _M_t(__u.release(), std::forward<deleter_type>(__u.get_deleter())) {}

/// Destructor, invokes the deleter if the stored pointer is not null.
~unique_ptr() noexcept
{
auto &__ptr = _M_t._M_ptr();
if (__ptr != nullptr)
get_deleter()(__ptr);
__ptr = pointer();
}

// Assignment.
unique_ptr &operator=(unique_ptr &&__u) noexcept
{
reset(__u.release());
get_deleter() = std::forward<deleter_type>(__u.get_deleter());
return *this;
}

/// Reset the %unique_ptr to empty, invoking the deleter if necessary.
unique_ptr &operator=(nullptr_t) noexcept
{
reset();
return *this;
}

// Observers.

/// Dereference the stored pointer.
typename add_lvalue_reference<element_type>::type
operator*() const
{
return *get();
}

/// Return the stored pointer.
pointer operator->() const noexcept
{
return get();
}

/// Return the stored pointer.
pointer get() const noexcept
{
return _M_t._M_ptr();
}

/// Return a reference to the stored deleter.
deleter_type &get_deleter() noexcept
{
return _M_t._M_deleter();
}

/// Return a reference to the stored deleter.
const deleter_type &get_deleter() const noexcept
{
return _M_t._M_deleter();
}

/// Return @c true if the stored pointer is not null.
explicit operator bool() const noexcept
{
return get() == pointer() ? false : true;
}

/// Release ownership of any stored pointer.
pointer release() noexcept
{
pointer __p = get();
_M_t._M_ptr() = pointer();
return __p;
}

void reset(pointer __p = pointer()) noexcept
{
using std::swap;
swap(_M_t._M_ptr(), __p);
if (__p != pointer())
get_deleter()(__p);
}

/// Exchange the pointer and deleter with another object.
void swap(unique_ptr &__u) noexcept
{
using std::swap;
swap(_M_t, __u._M_t);
}

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

下面是shared_ptr的类图。

img

如图,shared_ptr类几乎什么都没有做,它是继承了__shared_ptr__shared_ptr内部有一个类型为__shared_count类型的成员,__shared_count内部有类型为_Sp_counted_base*的成员。

_Sp_counted_base才是整个shared_ptr功能的核心,通过_Sp_counted_base控制引用计数来管理指向的内存,由图可见_Sp_counted_base内部不持有指向内存的指针,这里__shared_count内部的_Sp_counted_base*成员其实指向的是一个继承自_Sp_counted_base_Sp_counted_ptr类型的派生类对象,_Sp_counted_ptr类型内部持有指向内存的指针M_ptr,这样__shared_count内部就既可以控制引用计数,又可以在最后根据指向对象的指针释放托管内存。

注意的是:为什么_Sp_counted_ptr__shared_ptr内部都有指向对象的指针。实际上它们可以是不同的类型(只要它们之间存在隐式转换),这是shared_ptr的一大功能:

  1. 无需虚析构。假设BarFoo的基类,但是BarFoo都没有虚析构。

    1
    2
    3
    4
    5
    6
    7
    8
    // _Sp_counted_ptr._M_ptr 和 __shared_ptr._M_ptr 的类型都是 Foo*
    shared_ptr<Foo> sp1(new Foo);

    // 可以赋值, __shared_ptr._M_ptr 自动向上转型 up-cast
    // _Sp_counted_ptr._M_ptr 类型不变
    shared_ptr<Bar> sp2 = sp1;

    sp1.reset(); // 这时 Foo 对象的引用计数降为 1

    此后sp2仍然能安全地管理Foo对象的生命期,并安全完整地释放Foo,因为其_Sp_counted_ptr._M_ptr记住了Foo的实际类型。

  2. shared_ptr<void>可以指向并安全地管理(析构或防止析构)任何对象。

    1
    2
    3
    4
    5
    6
    7
    8
    // _Sp_counted_ptr._M_ptr 和 __shared_ptr._M_ptr 的类型都是 Foo*
    shared_ptr<Foo> sp1(new Foo);

    // 可以赋值, __shared_ptr._M_ptr 中的 Foo* 类型向 void* 自动转型
    // _Sp_counted_ptr._M_ptr 类型不变
    shared_ptr<void> sp2 = sp1;

    sp1.reset(); // 这时 Foo 对象的引用计数降为 1

    此后sp2仍然能安全地管理Foo对象的生命期,并安全完整地释放Foo,不会出现delete void*的情况,因为delete的是_Sp_counted_ptr._M_ptr而不是__shared_ptr._M_ptr

  3. 多继承。假设BarFoo的多个基类之一,那么:

    1
    2
    3
    4
    5
    6
    7
    shared_ptr<Foo> sp1(new Foo);

    // 这时 sp1._M_ptr 和 sp2._M_ptr 可能指向不同的地址
    // 因为 Bar subobject 在 Foo object 中的 offset 可能不为 0
    shared_ptr<Bar> sp2 = sp1;

    sp1.reset(); // 此时 Foo 对象的引用计数降为 1

    但是sp2仍然能安全地管理Foo对象的生命期,并安全完整地释放Foo,因为delete的不是Bar*,而是原来的 Foo*。换句话说,sp1._M_ptrsp2._M_ptr可能具有不同的值(当然它们的类型也不同)。

这里称_M_pi为管理对象,它内部的_M_ptr为托管对象,管理同一块托管对象的多个shared_ptr内部共用一个管理对象,,这里的多个shared_ptr可能是通过第一个shared_ptr拷贝或者移动而来,管理对象内部有两个成员变量_M_use_count_M_weak_count

_M_use_count表示托管对象的引用计数,控制托管对象什么时候析构和释放,当引用计数为0时调用托管对象的析构函数且释放内存。

_M_weak_count表示管理对象的引用计数,它的初始值比_M_use_count1,管理对象也是一个内存指针,这块指针是初始化第一个shared_ptrnew出来的,到最后也需要delete,所以使用_M_weak_count来控制管理对象什么时候析构,当weak_ptr析构时时,管理对象的引用计数_M_weak_count就会减1,当_M_weak_count0时,管理对象_M_pi就会析构且释放内存。

_M_use_count是如何加减的

Muse_count表示托管对象的引用计数,即当shared_ptr拷贝时会增加,当shared_ptr析构时会减少,看精简代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template <typename _Yp>
__shared_ptr(const __shared_ptr<_Yp, _Lp>& __r, element_type* __p) noexcept
: _M_ptr(__p), _M_refcount(__r._M_refcount) // never throws
{
}

__shared_count(const __shared_count& __r) noexcept : _M_pi(__r._M_pi)
{
if (_M_pi != 0) _M_pi->_M_add_ref_copy();
}

template <>
inline void _Sp_counted_base<_S_single>::_M_add_ref_copy()
{
++_M_use_count;
}

shared_ptr拷贝时,内部__shared_count类型的_M_refcount会进行拷贝,__shared_count的拷贝构造函数会调用_M_add_ref_copy()方法,_M_add_ref_copy()方法中会将_M_use_count1

这里再看下shared_ptr的赋值构造函数:

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
template <typename _Yp>
_Assignable<const shared_ptr<_Yp>&> operator=(const shared_ptr<_Yp>& __r) noexcept
{
this->__shared_ptr<_Tp>::operator=(__r);
return *this;
}

template <typename _Yp>
_Assignable<_Yp> operator=(const __shared_ptr<_Yp, _Lp>& __r) noexcept
{
_M_ptr = __r._M_ptr;
_M_refcount = __r._M_refcount; // __shared_count::op= doesn't throw
return *this;
}

__shared_count& operator=(const __shared_count& __r) noexcept
{
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if (__tmp != _M_pi)
{
if (__tmp != 0) __tmp->_M_add_ref_copy();
if (_M_pi != 0) _M_pi->_M_release();
_M_pi = __tmp;
}
return *this;
}

从代码中可见,shared_ptroperator=会调用__shared_ptroperator=进而调用__shared_countoperator=,从这里可以看出管理同一块托管对象的shared_ptr共用的同一个管理对象的指针。

_M_use_count是如何减为0的,可以猜想到shared_ptr析构时会调用__shared_count的析构函数,看精简代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~__shared_count() noexcept
{
if (_M_pi != nullptr) _M_pi->_M_release();
}

template <>
inline void _Sp_counted_base<_S_single>::_M_release() noexcept
{
if (--_M_use_count == 0)
{
_M_dispose();
if (--_M_weak_count == 0) _M_destroy();
}
}

virtual void _M_dispose() noexcept { delete _M_ptr; }

shared_ptr生命周期结束析构时会将引用计数减1,如果引用引用计数为0,会调用_M_dispose()函数进而释放托管对象内存。

_M_weak_count是如何加减的

上面的代码中可以看见_M_weak_count0时,会调用_M_destroy()函数,这里看看_M_weak_count是如何加减的。管理对象初始化时_M_weak_count的初始值为1

1
_Sp_counted_base() noexcept : _M_use_count(1), _M_weak_count(1) {}

注意当shared_ptr拷贝或者移动时_M_weak_count是不会增加的,它表示的是管理对象的计数,只有当__M_use_count为0时_M_weak_count才会减1,除此之外_M_weak_count的数值是由weak_ptr控制的。

由上面类图可以看见weak_ptr内部其实和shared_ptr内部持有的是同一个管理对象指针,即_Sp_counted_base的指针,当weak_ptr拷贝析构时候,_Sp_counted_base内部的_M_weak_count会相应加减。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__weak_count(const __weak_count& __r) noexcept : _M_pi(__r._M_pi)
{
if (_M_pi != nullptr) _M_pi->_M_weak_add_ref();
}

template <>
inline void _Sp_counted_base<_S_single>::_M_weak_add_ref() noexcept
{
++_M_weak_count;
}

~__weak_count() noexcept
{
if (_M_pi != nullptr) _M_pi->_M_weak_release();
}

template <>
inline void _Sp_counted_base<_S_single>::_M_weak_release() noexcept
{
if (--_M_weak_count == 0) _M_destroy();
}

virtual void _M_destroy() noexcept { delete this; }

从代码中可以看出,weak_ptr拷贝时_M_weak_count1,析构时_M_weak_count1,当_M_weak_count0时,表示不再需要管理对象来控制托管对象,调用_M_destroy()delete this来释放管理对象内存。

weak_ptrexpired()lock()做了什么

1
2
3
4
bool expired() const noexcept
{
return _M_refcount._M_get_use_count() == 0;
}

weak_ptrexpired()函数只是看了托管对象的引用计数是否为0,为0返回true

1
2
3
4
5
6
7
8
9
10
__shared_ptr<_Tp, _Lp> lock() const noexcept
{
return __shared_ptr<element_type, _Lp>(*this, std::nothrow);
}

__shared_ptr(const __weak_ptr<_Tp, _Lp>& __r, std::nothrow_t)
: _M_refcount(__r._M_refcount, std::nothrow)
{
_M_ptr = _M_refcount._M_get_use_count() ? __r._M_ptr : nullptr;
}

weak_ptrlock()函数是打算返回一个shared_ptr对象来延长托管对象的生命周期,这里返回后需要判断返回值是否为nullptr

shared_from_this()是如何实现的

精简代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class enable_shared_from_this
{
__shared_ptr<_Tp, _Lp> shared_from_this()
{
return __shared_ptr<_Tp, _Lp>(this->_M_weak_this);
}

__shared_ptr<const _Tp, _Lp> shared_from_this() const
{
return __shared_ptr<const _Tp, _Lp>(this->_M_weak_this);
}

mutable weak_ptr<_Tp> _M_weak_this;
};

使用shared_from_this()的类需要继承enable_shared_from_this类,enable_shared_from_this类中持有一个类型为weak_ptr的成员_M_weak_this,调用shared_from_this()就是将内部持有的weak_ptr转成了shared_ptr

为什么建议使用make_shared()函数

我们已经看到了,shared_ptr内部维护了两个指针,如果你直接调用构造函数,就想这样:

1
2
class Foo;
auto sp = shared_ptr<Widget>(new Foo());

这里实际分配了两次内存,第一次是调用new Foo()的时候,第二次则是在shared_ptr构造函数的内部构造_Sp_counted_base的时候。分配内存是很昂贵的操作,所以标准库提供了make_shared()函数,让你一次分配全部所需的内存:

1
2
3
4
5
6
7
8
template<typename _Tp, _Lock_policy _Lp, typename... _Args>
inline __shared_ptr<_Tp, _Lp>
__make_shared(_Args&&... __args)
{
typedef typename std::remove_const<_Tp>::type _Tp_nc;
return std::__allocate_shared<_Tp, _Lp>
(std::allocator<_Tp_nc>(), std::forward<_Args>(__args)...);
}

现在用make_shared()的话,可以一次分配一块足够大的内存,供Foo_Sp_counted_base对象容身。

智能指针的线程安全性分析
  1. 同一个的shared_ptr对象可以被多个线程同时读取(只读)

    对同一个对象的并发读,显然是线程安全的。

  2. 不同的shared_ptr对象可以被多个线程同时读写

    如果这不同的shared_ptr对象管理的是不同的对象资源,显然并发读写是线程安全的。

    若它们指向的是同一个对象,这需要它们共同维护一份在堆上的引用计数资源。而shared_ptr对象对于这份引用计数资源的读写使用了原子性操作,因此,也是线程安全的。

    容易出现问题的是,从shared_ptr构造weak_ptr或者从weak_ptr构造shared_ptr的情况。

    • shared_ptr构造weak_ptr

      由于weak_ptr的构造过程中并不涉及引用计数资源的改变(实际上不改变的是shared_ptr对象计数变量_M_use_count_,只是改变了weak_ptr对象计数变量_M_weak_count),和前面的分析一样,使用了原子操作,也是线程安全的。

    • weak_ptr构造shared_ptr

      而这一过程,会改变引用计数资源(增加shared_ptr对象计数变量_M_use_count),但是实际上也实现了线程安全的修改。可以去参考源码的实现。

  3. 同一个shared_ptr对象不可以被多个线程同时读写(有写)

    shared_ptr<Foo>包含两个成员,一个是指向Foo的指针_M_ptr,另一个是_Sp_counted_base*指针,指向堆上的_Sp_counted_base对象。因为这两个数据成员读写操作不是原子化,所以使用多个线程读写同一个shared_ptr对象需要加锁,也就是说不是线程安全的。

自己动手实现一个基本的shared_ptr
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
#include <iostream>
#include <functional>

template <class T>
class Deleter
{
public:
using DefaultFunc = std::function<void(T *)>;
Deleter() : func_(std::bind(&Deleter::defaultFunc, this, std::placeholders::_1))
{
std::cout << "Deleter::Deleter()" << std::endl;
}
template <class D>
Deleter(D func) : func_(func)
{
std::cout << "Deleter::Deleter(D func)" << std::endl;
}

void operator()(T *p)
{
func_(p);
}

private:
DefaultFunc func_;
void defaultFunc(T *p)
{
std::cout << "Deleter::defaultFunc" << std::endl;
delete p;
}
};

template <class T>
class Ref_count_base
{
private:
T *ptr_;
int count_;
Deleter<T> deleter_;

public:
Ref_count_base(T *p) : ptr_(p), count_(1)
{
std::cout << "Ref_count_base::Ref_count_base(T *p)" << std::endl;
}

template <class D>
Ref_count_base(T *p, D deleter) : ptr_(p), count_(1), deleter_(deleter)
{
std::cout << "Ref_count_base::Ref_count_base(T *p, D deleter)" << std::endl;
}

~Ref_count_base()
{
std::cout << "Ref_count_base::~Ref_count_base()" << std::endl;

deleter_(ptr_);
}

int increase()
{
return count_++;
}

int reduce()
{
return --count_;
}

int getCount()
{
return count_;
}
};

template <class T>
class Shared_ptr
{
private:
T *ptr_;
Ref_count_base<T> *ref_count_;

public:
Shared_ptr() : ptr_(nullptr), ref_count_(nullptr)
{
std::cout << "Shared_ptr::Shared_ptr()" << std::endl;
}

explicit Shared_ptr(std::nullptr_t)
{
std::cout << "Shared_ptr::Shared_ptr(std::nullptr_t)" << std::endl;
Shared_ptr<T> temp;
swap(temp);
}

explicit Shared_ptr(T *p) : ptr_(p), ref_count_(new Ref_count_base<T>(p))
{
std::cout << "Shared_ptr::Shared_ptr(T *p)" << std::endl;
}

template <class D>
explicit Shared_ptr(T *p, D deleter) : ptr_(p), ref_count_(new Ref_count_base<T>(p, deleter))
{
std::cout << "Shared_ptr::Shared_ptr(T *p, D deleter)" << std::endl;
}

~Shared_ptr()
{
std::cout << "Shared_ptr::~Shared_ptr()" << std::endl;
if (ref_count_ && ref_count_->reduce() == 0)
delete ref_count_;
}

Shared_ptr(const Shared_ptr<T> &sp) : ptr_(sp.ptr_), ref_count_(sp.ref_count_)
{
std::cout << "Shared_ptr::Shared_ptr(const Shared_ptr<T> &sp)" << std::endl;
if (ref_count_)
ref_count_->increase();
}

Shared_ptr &operator=(const Shared_ptr<T> &sp)
{
std::cout << "Shared_ptr::operator=(const Shared_ptr<T> &sp)" << std::endl;
Shared_ptr<T> temp(sp);
swap(temp);
return *this;
}

Shared_ptr &operator=(std::nullptr_t)
{
std::cout << "Shared_ptr::operator=(std::nullptr_t)" << std::endl;
Shared_ptr<T> temp;
swap(temp);
return *this;
}

T &operator*()
{
if (ptr_)
return *ptr_;
}

T *operator->()
{
if (ptr_)
return ptr_;
}

T &operator[](ptrdiff_t i)
{
return ptr_[i];
}

bool operator==(Shared_ptr<T> &rhs) const
{
return ptr_ == rhs.ptr_;
}

operator bool() const
{
return ptr_ != nullptr;
}

T *get() const
{
return ptr_;
}

int use_count() const
{
if (ref_count_)
return ref_count_->getCount();
return 0;
}

void swap(Shared_ptr<T> &sp)
{
std::cout << "Shared_ptr::swap(Shared_ptr<T> &sp)" << std::endl;
using std::swap;
swap(ptr_, sp.ptr_);
swap(ref_count_, sp.ref_count_);
}
};

template <class T>
void swap(Shared_ptr<T> &lhs, Shared_ptr<T> &rhs)
{
lhs.swap(rhs);
}

/* 以下是测试代码 */
/* 以下是测试代码 */
/* 以下是测试代码 */

class Point
{
public:
explicit Point(int i = 0) : val(i)
{
std::cout << "Point(int i = 0)" << std::endl;
}
~Point()
{
std::cout << "~Point()" << std::endl;
}

Point &operator=(int i)
{
std::cout << "Point::operator=(int i)" << std::endl;
val = i;
return *this;
}
int getVal() const
{
return val;
}

private:
int val;
};

class myDeleter1
{
public:
void operator()(Point *p)
{
delete[] p;
}
};

void myDeleter2(Point *p)
{
std::cout << "myDeleter2(Point *p)" << std::endl;
delete[] p;
}

auto myDeleter3 = [](Point *p) { delete[] p; };

int main()
{
Shared_ptr<Point> p1(new Point[2], myDeleter3);

std::cout << p1[0].getVal() << std::endl;
p1[0] = 10;
std::cout << p1[0].getVal() << std::endl;

std::cout << "p1.use_count: " << p1.use_count() << std::endl;

Shared_ptr<Point> p2 = p1;
Shared_ptr<Point> p3;
p3 = p2;
std::cout << "p1.use_count: " << p1.use_count() << std::endl;
std::cout << "p2.use_count: " << p2.use_count() << std::endl;
std::cout << "p3.use_count: " << p3.use_count() << std::endl;

if (p1 == p2)
std::cout << "p1 == p2" << std::endl;
else
std::cout << "p1 != p2" << std::endl;

if (p1)
std::cout << "p1 为真" << std::endl;
p1 = nullptr;
if (!p1)
std::cout << "p1 为假" << std::endl;

std::cout << "p1.use_count: " << p1.use_count() << std::endl;
std::cout << "p2.use_count: " << p2.use_count() << std::endl;
std::cout << "p3.use_count: " << p3.use_count() << std::endl;

return 0;
}

// 运行结果为

Point::Point(int i = 0)
Point::Point(int i = 0)
Deleter::Deleter(D func)
Ref_count_base::Ref_count_base(T *p, D deleter)
Shared_ptr::Shared_ptr(T *p, D deleter)
0
Point::operator=(int i)
10
p1.use_count: 1
Shared_ptr::Shared_ptr(const Shared_ptr<T> &sp)
Shared_ptr::Shared_ptr()
Shared_ptr::operator=(const Shared_ptr<T> &sp)
Shared_ptr::Shared_ptr(const Shared_ptr<T> &sp)
Shared_ptr::swap(Shared_ptr<T> &sp)
Shared_ptr::~Shared_ptr()
p1.use_count: 3
p2.use_count: 3
p3.use_count: 3
p1 == p2
p1 为真
Shared_ptr::operator=(std::nullptr_t)
Shared_ptr::Shared_ptr()
Shared_ptr::swap(Shared_ptr<T> &sp)
Shared_ptr::~Shared_ptr()
p1 为假
p1.use_count: 0
p2.use_count: 2
p3.use_count: 2
Shared_ptr::~Shared_ptr()
Shared_ptr::~Shared_ptr()
Ref_count_base::~Ref_count_base()
Point::~Point()
Point::~Point()
Shared_ptr::~Shared_ptr()
智能指针使用注意事项
  1. 尽量用make_shared/make_unique

    std::shared_ptr在实现的时候使用的ref count技术,因此内部会有一个计数器(控制块,用来管理数据)和一个指针,指向数据。因此在执行std::shared_ptr<Widget> p2(new Widget)的时候,首先会申请数据的内存,然后申请内控制块,因此是两次内存申请,而std::make_shared<Widget>()则是只执行一次内存申请,将数据和控制块的申请放到一起。

  2. 不要使用相同的内置指针来初始化(或者reset)多个智能指针

  3. 不要delete get()返回的指针

  4. 不要用get()初始化或reset另一个智能指针

  5. 智能指针管理的资源它只会默认删除new分配的内存,如果不是new分配的则要传递给其一个删除器

    以下代码试图将malloc产生的动态内存交给shared_ptr管理,显然是有问题的,所以我们需要自定义删除器传递给shared_ptr

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 错误的
    {
    int* pi = (int*)malloc(4 * sizeof(int));
    shared_ptr<int> sp(pi);
    }
    // 正确的
    {
    int* pi = (int*)malloc(4 * sizeof(int));
    shared_ptr<int> sp(pi, [](int* p){ free(p); });
    }
  6. 不要把一个原生指针给多个shared_ptr或者多个unique_ptr管理

    在使用原生指针对智能指针初始化的时候,智能指针对象都视原生指针为自己管理的资源。换句话意思就说:初始化多个智能指针之后,这些智能指针都担负起释放内存的作用。那么就会导致该原生指针会被释放多次!!

    1
    2
    3
    4
    // p1, p2 析构的时候都会释放 ptr, 同一内存被释放多次!
    int* ptr = new int;
    shared_ptr<int> p1(ptr);
    shared_ptr<int> p2(ptr);