RAII与智能指针

1.RAII思想

RAII是C++内存管理的一种思想,资源获取视为初始化。使用对象封装资源的获取与释放操作,对象初始化时调用构造函数完成资源获取,对象离开作用域自动调用析构函数完成资源释放,避免了忘记调用销毁函数的情况,降低内存泄漏风险。如下例子:

1
2
3
4
5
6
7
8
9
10
11
template <typename T>
class MyLock {
public:
explicit MyLock(T& mutex)
:_mutex(mutex) {
_mutex.lock();
}
~MyLock() {
_mutex.unlock();
}
};

这段代码可以保证初始化的时候加锁,生命周期结束后自动释放锁,同时天生异常安全,因为C++异常栈展开时会调用局部对象的析构函数。

2.智能指针

2.1 基本介绍

智能指针就是采用RAII思想实现的资源管理模板类,由标准库提供,需要包含<memory>头文件。

名字 管理方式 特点
std::unique_ptr 独享 某一时刻只能有一个对象占有资源;
不可以拷贝与赋值(会造成double free),但可以移动;
离开作用域立即释放资源;常与原始指针联用
std::shared_ptr 共享 支持多个对象共享资源;
内部维护引用计数,可以拷贝或赋值,会造成引用计数变化,会当计数为0时释放资源;
常于std::weak_ptr联用

2.2 基本用法

2.2.1 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
struct Foo {
Foo() { std::cout << "Foo\n"; }
~Foo() { std::cout << "~Foo\n"; }
};

// 最常用的初始化
std::unique_ptr<Foo> p1(std::make_unique<Foo>());

// 绑定new指针
std::unique_ptr<Foo> p2(new Foo());

// 绑定数组
std::unique_ptr<int[]> parr(new int[10]);

// 类删除器
struct D {
D() {};
D(const D&) { std::cout << "D copy ctor\n"; }
D(D&) { std::cout << "D non-const copy ctor\n";}
D(D&&) { std::cout << "D move ctor \n"; }
void operator()(Foo* p) const {
std::cout << "D is deleting a Foo\n";
delete p;
};
};

std::unique_ptr<Foo, D> p3(std::make_unique<Foo>(), D()); // 此时发生D移动构造
D d;
std::unique_ptr<Foo, D> p4(std::make_unique<Foo>(), d); // 此时发生D拷贝构造

// 函数删除器
auto deleter = [](int* ptr) {
std::cout << "[deleter called.]" << std::endl;
delete ptr;
}

std::unique_ptr<int, decltype(deleter)> p5(new int, deleter); // 使用lambda表达式

手动释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::unique_ptr<Foo> p1(std::make_unique<Foo>());

// 移出控制权,但不真正销毁对象。之后get()返回nullptr
auto p = p1.release();

// 销毁旧的,赋值新的
p1.reset(new Foo());
// 移除控制权并销毁对象
p1.reset(nullptr);

// 交换被管理的对象以及删除其
std::unique_ptr<Foo> up1(new Foo(1));
std::unique_ptr<Foo> up2(new Foo(2));
up1.swap(up2)

2.2.2 shared_ptr

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Foo {
Foo() { std::cout << "Foo\n"; }
~Foo() { std::cout << "~Foo\n"; }
};

// 最常用的写法
std::shared_ptr<Foo> p1(std::make_shared<Foo>());

// 绑定new指针
std::shared_ptr<Foo> p2(new Foo());

// 指定分配器
std::shared_ptr<Foo> p3(new Foo, [](auto p) {
std::cout << "Call delete from lambda...\n";
delete p;
});

手动释放

1
2
3
4
5
6
7
8
// 销毁旧的,赋值新的
p1.reset(new Foo());
// 移除控制权并销毁对象
p1.reset(nullptr);
// 不支持的操作
p1.reset(p2);

// 没有release操作

2.3 shared源码分析

1
源码来自:/usr/include/c++/9/tr1/shared_ptr.h,对无关内容做了适当删减

2.3.1 类整体结构

shared_ptr为资源管理对象,内部维护对应的资源指针。shared_ptr对象销毁时会根据递减内部的引用计数来决定是否真正销毁资源,同时支持多个对象共同享有同一份资源,只需要改变引用计数即可。整体类图如下:

shared类图

2.3.2 shared_ptr分析

a> 初始化过程

__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
template<typename _Tp, _Lock_policy _Lp>
class __shared_ptr
{
public:
typedef _Tp element_type;
// 默认构造函数
__shared_ptr()
: _M_ptr(0), _M_refcount() // never throws
{ }
// 单模板指针构造函数
template<typename _Tp1>
explicit
__shared_ptr(_Tp1* __p)
: _M_ptr(__p), _M_refcount(__p)
{
__glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)
typedef int _IsComplete[sizeof(_Tp1)];
// 开启enable_shared_from_this
__enable_shared_from_this_helper(_M_refcount, __p, __p);
}
// 指定删除器的构造函数
template<typename _Tp1, typename _Deleter>
__shared_ptr(_Tp1* __p, _Deleter __d)
: _M_ptr(__p), _M_refcount(__p, __d)
{
__glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)
// 开启enable_shared_from_this
__enable_shared_from_this_helper(_M_refcount, __p, __p);
}
private:
_Tp* _M_ptr; // 资源指针
__shared_count<_Lp> _M_refcount; // 资源管理对象
};

从源码可以得到shared_ptr指针初始化会将参数指针(来自new运算符或者是make_shared得到)初始化给_M_ptr成员,再用参数指针去初始化__shared_count<_Lp>类,该类的构造函数如下:

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
template<_Lock_policy _Lp = __default_lock_policy>
class __shared_count
{
public:
// 无参构造函数
__shared_count()
: _M_pi(0) // nothrow
{ }
// 单参数构造函数
template<typename _Ptr>
__shared_count(_Ptr __p) : _M_pi(0)
{
__try
{
typedef typename std::tr1::remove_pointer<_Ptr>::type _Tp;
// 动态分配了一个生命期管理对象
_M_pi = new _Sp_counted_base_impl<_Ptr, _Sp_deleter<_Tp>, _Lp>(
__p, _Sp_deleter<_Tp>());
}
__catch(...)
{
delete __p;
__throw_exception_again;
}
}
// 指定删除器的构造函数
template<typename _Ptr, typename _Deleter>
__shared_count(_Ptr __p, _Deleter __d) : _M_pi(0)
{
__try
{
_M_pi = new _Sp_counted_base_impl<_Ptr, _Deleter, _Lp>(__p, __d);
}
__catch(...)
{
__d(__p); // Call _Deleter on __p.
__throw_exception_again;
}
}
private:
friend class __weak_count<_Lp>;
_Sp_counted_base<_Lp>* _M_pi; // 资源管理父类指针
};

可以看见在__shared_count<_Lp>类的构造函数中调用new运算符申请了一个_Sp_counted_base_impl<_Ptr, _Sp_deleter<_Tp>, _Lp>对象,该类的构造函数如下:

1
2
3
4
5
6
7
8
9
10
11
template<typename _Ptr, typename _Deleter, _Lock_policy _Lp>
class _Sp_counted_base_impl: public _Sp_counted_base<_Lp>
{
public:
// 构造函数--默认构造父类
_Sp_counted_base_impl(_Ptr __p, _Deleter __d)
: _M_ptr(__p), _M_del(__d) { }
private:
_Ptr _M_ptr; // 被管理资源指针
_Deleter _M_del; // 资源释放器
};

该类public继承自_Sp_counted_base类,并且默认构造父类,父类构造函数如下:

1
2
3
4
5
6
7
8
9
10
template<_Lock_policy _Lp = __default_lock_policy>
class _Sp_counted_base: public _Mutex_base<_Lp>
{
public:
// 默认构造函数
_Sp_counted_base(): _M_use_count(1), _M_weak_count(1) { }
private:
_Atomic_word _M_use_count; // shared引用计数
_Atomic_word _M_weak_count; // weak引用计数
};

可以看见将_M_use_count和_M_weak_count成员默认初始化为1,表示当前只被引用了一次,此时shared_ptr整体类布局如下:

shared_ptr类布局

b> 销毁过程

__shared_ptr类不存在析构函数,直接看其组合类_shared_count的析构函数,代码如下:

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
template<_Lock_policy _Lp = __default_lock_policy>
class __shared_count
{
public:
// 单参数构造函数
template<typename _Ptr>
__shared_count(_Ptr __p) : _M_pi(0)
{
__try
{
typedef typename std::tr1::remove_pointer<_Ptr>::type _Tp;
// 动态分配了一个生命期管理对象
_M_pi = new _Sp_counted_base_impl<_Ptr, _Sp_deleter<_Tp>, _Lp>(
__p, _Sp_deleter<_Tp>());
}
__catch(...)
{
delete __p;
__throw_exception_again;
}
}
// 析构函数
~__shared_count() // nothrow
{
if (_M_pi != 0)
_M_pi->_M_release();
}
private:
friend class __weak_count<_Lp>;
_Sp_counted_base<_Lp>* _M_pi; // 资源管理父类指针--多态
};

析构函数中调用_Sp_counted_base_impl类的_M_release()函数,但是这个函数是在父类_Sp_counted_base_中实现的非纯虚函数,代码如下:

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
template<_Lock_policy _Lp = __default_lock_policy>
class _Sp_counted_base: public _Mutex_base<_Lp>
{
public:
_Sp_counted_base(): _M_use_count(1), _M_weak_count(1) { }
void _M_release() // nothrow
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
// _M_use_count-- == 1
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
// 这里调用子类_Sp_counted_base_impl的_M_dispose()
_M_dispose();
if (_Mutex_base<_Lp>::_S_need_barriers)
{
__atomic_thread_fence (__ATOMIC_ACQ_REL);
}
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
// 这里调用父类_Sp_counted_base的_M_destroy()
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
_M_destroy();
}
}
}
virtual void _M_destroy() // nothrow
{ delete this; }
private:
_Atomic_word _M_use_count; // shared引用计数
_Atomic_word _M_weak_count; // weak引用计数
};

M_release()函数中首先对_M_use_count进行减1操作,如果减1之前为1,调用子类_Sp_counted_base_impl的_M_dispose()函数,再对_M_weak_count进行减1操作,同样判断减1前是否为1,如果是,调用父类_Sp_counted_base的_M_destroy()函数。代码如下:

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
// 子类
template<typename _Ptr, typename _Deleter, _Lock_policy _Lp>
class _Sp_counted_base_impl: public _Sp_counted_base<_Lp>
{
public:
// 构造函数--默认构造父类
_Sp_counted_base_impl(_Ptr __p, _Deleter __d)
: _M_ptr(__p), _M_del(__d) { }
// 销毁资源
virtual void _M_dispose() // nothrow
{ _M_del(_M_ptr); }
private:
_Ptr _M_ptr; // 被管理资源指针
_Deleter _M_del; // 资源释放器
};

// 父类
template<_Lock_policy _Lp = __default_lock_policy>
class _Sp_counted_base: public _Mutex_base<_Lp>
{
public:
// 默认构造函数
_Sp_counted_base(): _M_use_count(1), _M_weak_count(1) { }
// 销毁资源管理对象
virtual void _M_destroy() // nothrow
{ delete this; }
private:
_Atomic_word _M_use_count; // shared引用计数
_Atomic_word _M_weak_count; // weak引用计数
};

可以看见,_M_dispose()中调用_M_del销毁器销毁被管理的资源对象,_M_destroy()中调用free销毁资源管理对象,可以得出结论:

  • _M_use_count计数器控制被管理资源对象的声明周期
  • _M_weak_count计数器控制资源管理对象的声明周期

c> 拷贝过程分析

__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
template<typename _Tp, _Lock_policy _Lp>
class __shared_ptr
{
public:
// shared_ptr拷贝构造函数
template<typename _Tp1>
__shared_ptr(const __shared_ptr<_Tp1, _Lp>& __r)
: _M_ptr(__r._M_ptr), _M_refcount(__r._M_refcount) // never throws
{ __glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>) }
// weak_ptr拷贝构造函数--为了支持lock()操作
template<typename _Tp1>
explicit __shared_ptr(const __weak_ptr<_Tp1, _Lp>& __r)
: _M_refcount(__r._M_refcount) // may throw
{
__glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)
// 因为_M_refcount(__r._M_refcount)可能会抛出异常,所以需要执行后再进行指针转移
_M_ptr = __r._M_ptr;
}
// 拷贝赋值运算符
template<typename _Tp1>
__shared_ptr& operator=(const __shared_ptr<_Tp1, _Lp>& __r) // never throws
{
_M_ptr = __r._M_ptr;
_M_refcount = __r._M_refcount; // __shared_count::op= doesn't throw
return *this;
}
private:
_Tp* _M_ptr; // 资源指针
__shared_count<_Lp> _M_refcount; // 资源管理对象
};

可以看见支持使用shared_ptr和weak_ptr进行拷贝,后者是在weak_ptr中调用lock()函数时调用的。

初始化列表_M_refcount(__r._M_refcount)都调用了__shared_count<_Lp>类的拷贝构造函数进行拷贝;
_M_refcount = _r.M_refcount调用了_shared_count<_Lp>类的赋值运算函数;

代码如下:

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
template<_Lock_policy _Lp = __default_lock_policy>
class __shared_count
{
public:
// shared_count拷贝构造函数
__shared_count(const __shared_count& __r)
: _M_pi(__r._M_pi) // nothrow
{
if (_M_pi != 0)
// use_count++
_M_pi->_M_add_ref_copy();
/*
void _M_add_ref_copy()
{ __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }
*/
}
// weak_count拷贝构造函数
explicit
__shared_count(const __weak_count<_Lp>& __r)
: _M_pi(__r._M_pi)
{
if (_M_pi != 0)
// weak_count++
_M_pi->_M_add_ref_lock();
/*
template<> inline void _Sp_counted_base<_S_mutex>::_M_add_ref_lock()
{
__gnu_cxx::__scoped_lock sentry(*this);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, 1) == 0)
{
_M_use_count = 0;
__throw_bad_weak_ptr();
}
}
*/
else
__throw_bad_weak_ptr();
}
// 拷贝赋值运算符
__shared_count&
operator=(const __shared_count& __r) // nothrow
{
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
// 判断自赋值
if (__tmp != _M_pi)
{
if (__tmp != 0)
// _M_use_count++
__tmp->_M_add_ref_copy();
if (_M_pi != 0)
// 如果_M_use_count-- == 1调用Deleter销毁_M_ptr被管理对象
// 如果_M_weak_count-- == 1调用free销毁_Sp_counted_base_impl管理对象
// 换句话说,__shared_count内部的use_count主要用来标记被管理对象的生命周期,weak_count主要用来标记管理对象的生命周期。
_M_pi->_M_release();
_M_pi = __tmp;
}
return *this;
}
private:
friend class __weak_count<_Lp>;
_Sp_counted_base<_Lp>* _M_pi; // 资源管理父类指针--多态
};

可以看见先是对资源管理指针进行值拷贝,这里也就说明了当多个shared_ptr指向同一块资源时,他们之间只有一份共享资源管理对象;

如果是用shared_ptr进行拷贝,将use_count引用计数加1。

如果是用weak_ptr进行拷贝,此时就是weak_ptr调用lock()时,也会将use_count引用计数加1,并且会判断加之前是否为0,如果是则抛出异常。

d> reset函数实现

关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// reset()函数实现
void reset() // never throws
{ __shared_ptr().swap(*this); }
// 交换指针函数实现
void swap(__shared_ptr<_Tp, _Lp>& __other) // never throws
{
std::swap(_M_ptr, __other._M_ptr);
_M_refcount._M_swap(__other._M_refcount);
}
// reset有参函数实现
template<typename _Tp1>
void reset(_Tp1* __p) // _Tp1 must be complete.
{
// Catch self-reset errors.
_GLIBCXX_DEBUG_ASSERT(__p == 0 || __p != _M_ptr);
__shared_ptr(__p).swap(*this);
}
// reset有参函数实现
template<typename _Tp1, typename _Deleter>
void reset(_Tp1* __p, _Deleter __d)
{ __shared_ptr(__p, __d).swap(*this); }

这里的实现很巧妙,根据参数不同原地构造不同的局部对象,使用swap交换指针所指向的内容,交换后新构造出来的对象变为要销毁的对象,在函数结束时自动调用析构函数完成销毁,充分体现了RAII思想。

2.3.3 weak_ptr分析

a> 初始化过程

__weak_ptr的构造函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename _Tp, _Lock_policy _Lp>
class __weak_ptr
{
public:
typedef _Tp element_type;
// 无参构造函数
__weak_ptr()
: _M_ptr(0), _M_refcount() // never throws
{ }
private:
_Tp* _M_ptr; // 资源指针
__weak_count<_Lp> _M_refcount; // 资源管理对象
};

weak_ptr只能默认初始化,过程很简单,weak_count中的无参构造函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
template<_Lock_policy _Lp>
class __weak_count
{
public:
// 无参构造函数
__weak_count()
: _M_pi(0) // nothrow
{ }
private:
friend class __weak_count<_Lp>;
_Sp_counted_base<_Lp>* _M_pi; // 资源管理父类指针
};

可以看见将指针赋值为0,基本上什么也没做,其实这种用法不常见,最常见的还是绑定到shared_ptr指针上,也就是调用拷贝构造函数。

b> 拷贝过程分析

__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
template<typename _Tp, _Lock_policy _Lp>
class __weak_ptr
{
public:
typedef _Tp element_type;
// weak_ptr拷贝构造函数
template<typename _Tp1>
__weak_ptr(const __weak_ptr<_Tp1, _Lp>& __r)
: _M_refcount(__r._M_refcount) // never throws
{
__glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)
_M_ptr = __r.lock().get();
}
// shared_ptr拷贝构造函数
template<typename _Tp1>
__weak_ptr(const __shared_ptr<_Tp1, _Lp>& __r)
: _M_ptr(__r._M_ptr), _M_refcount(__r._M_refcount) // never throws
{ __glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>) }
// weak_ptr赋值运算符重载
template<typename _Tp1>
__weak_ptr&
operator=(const __weak_ptr<_Tp1, _Lp>& __r) // never throws
{
_M_ptr = __r.lock().get();
_M_refcount = __r._M_refcount;
return *this;
}
// shared_ptr赋值运算符重载
template<typename _Tp1>
__weak_ptr&
operator=(const __shared_ptr<_Tp1, _Lp>& __r) // never throws
{
_M_ptr = __r._M_ptr;
_M_refcount = __r._M_refcount;
return *this;
}
private:
_Tp* _M_ptr; // 资源指针
__weak_count<_Lp> _M_refcount; // 资源管理对象
};

可以看见可以使用使用shared_ptr和weak_ptr两种指针进行拷贝初始化和拷贝赋值。

这里只讨论使用shared_ptr初始化weak_ptr,会调用__weak_count<_Lp>类的shared版本的拷贝构造函数,如下:

1
2
3
4
5
6
7
8
9
10
11
// shared_count拷贝构造函数  
__weak_count(const __shared_count<_Lp>& __r)
: _M_pi(__r._M_pi) // nothrow
{
if (_M_pi != 0)
_M_pi->_M_weak_add_ref();
/*
_M_weak_add_ref() // nothrow
{ __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1); }
*/
}

可以看见会将资源管理对象的指针直接给weak_count,再将_M_weak_count引用计数加1。可以得到结论:weak_ptr绑定shared_ptr不会影响资源引用计数,weak_ptr和shared_ptr共用一份资源管理对象。weak_ptr与shared_ptr内部结构类似,如图所示:

weak_ptr类布局

c> 销毁过程分析

_weak_count析构函数如下:

1
2
3
4
5
6
// 析构函数      
~__weak_count() // nothrow
{
if (_M_pi != 0)
_M_pi->_M_weak_release();
}

_Sp_counted_base类中的_M_weak_release()代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
_M_weak_release() // nothrow
{
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
// _M_weak_count-- == 1
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
if (_Mutex_base<_Lp>::_S_need_barriers)
{
// See _M_release(),
// destroy() must observe results of dispose()
__atomic_thread_fence (__ATOMIC_ACQ_REL);
}
_M_destroy();
/*
virtual void _M_destroy() // nothrow
{ delete this; }
*/
}
}

可以看见weak_ptr的销毁过程只递减了_M_weak_count引用计数,并判断决定是否销毁资源管理对象。

d> lock函数实现

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__shared_ptr<_Tp, _Lp> lock() const // never throws
{
#ifdef __GTHREADS
// 通过_M_use计数是否为0判断weak_ptr是否有效
if (expired())
return __shared_ptr<element_type, _Lp>();
__try
{
// 强制类型转换
return __shared_ptr<element_type, _Lp>(*this);
}
__catch(const bad_weak_ptr&)
{
// Q: How can we get here?
// A: Another thread may have invalidated r after the
// use_count test above.
return __shared_ptr<element_type, _Lp>();
}

原理就是调用shared_ptr可以接受weak_ptr版本的拷贝构造函数来初始化一个shared_ptr返回,并且会造成_M_use_count引用计数加1。

2.4 shared指针问题

2.4.1 循环引用

a> 错误示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct C {
std::shared_ptr<C> m_child;
std::shared_ptr<C> m_parent;
};

int main() {
auto parent = std::make_shared<C>();
auto child = std::make_shared<C>();

// 建立互相引用
parent->m_child = child;
child->m_parent = parent;

// 尝试释放
parent.reset();
child.reset();

return 0;
}

上面代码parent和child指针都不会被释放,在释放parent时,存在child指向,释放child时,存在parent指向。

b> 解决办法

将结构C其中一个shared_ptr改为weak_ptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct C {
std::shared_ptr<C> m_child;
std::weak_ptr<C> m_parent;
};

int main() {
auto parent = std::make_shared<C>();
auto child = std::make_shared<C>();

// 建立互相引用
parent->m_child = child;
child->m_parent = parent;

// 尝试释放,必须先释放parent,才能将child引用计数降到1
parent.reset(); // parent可以被释放,因为child中指向它的是弱引用,不会增加引用计数
child.reset(); // child可以被释放,因为parent中指向它的已经被释放了

return 0;
}

2.4.2 this问题

shared_ptr能够安全管理资源的一个前提是,多个共享资源的shared_ptr所使用的资源管理对象是同一个。如果不是同一个,在销毁时会对同一份资源进行double free。

a> 错误示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Thing {
public:
void modify() {
std::shared_ptr<Thing> p2(this);
// first free
}
};

int main()
{
std::cout << "begin" << std::endl;
{
std::shared_ptr<Thing> p1(new Thing);
p1->modify();
// double free
}
std::cout << "end" << std::endl;

return 0;
}

运行结果如下:

double free

代码会出现双重释放错误,在资源内部使用资源自身this初始化shared_ptr会在shared_ptr的构造函数中使用new重新申请一个资源管理对象。此时两个不同的资源管理对象同时管理一份资源,所以会造成对同一资源的二次释放错误。

b> 解决办法

C++引入了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
24
25
26
27
28
29
30
31
template<typename _Tp>
class enable_shared_from_this
{
protected:
enable_shared_from_this() { }
enable_shared_from_this(const enable_shared_from_this&) { }
enable_shared_from_this& operator=(const enable_shared_from_this&)
{ return *this;
~enable_shared_from_this() { }
public:
shared_ptr<_Tp> shared_from_this()
{ return shared_ptr<_Tp>(this->_M_weak_this); }
shared_ptr<const _Tp> shared_from_this() const
{ return shared_ptr<const _Tp>(this->_M_weak_this); }
private:
template<typename _Tp1>
void _M_weak_assign(_Tp1* __p, const __shared_count<>& __n) const
{ _M_weak_this._M_assign(__p, __n); }
template<typename _Tp1>
// _shared_ptr的构造函数中被调用
friend void __enable_shared_from_this_helper(const __shared_count<>& __pn,
const enable_shared_from_this* __pe,
const _Tp1* __px)
{
if (__pe != 0)
__pe->_M_weak_assign(const_cast<_Tp1*>(__px), __pn);
}

mutable weak_ptr<_Tp> _M_weak_this;
};
}

只需要让出现问题的Thing类继承自enable_shared_from_this即可在内部维护一个weak_ptr,需要使用指针时,只需要调用shared_from_this()成员即可。修改后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Thing 
: public std::enable_shared_from_this<Thing> {
public:
void modify() {
std::shared_ptr<Thing> p2(shared_from_this());
// first free
}
};

int main()
{
std::cout << "begin" << std::endl;
{
std::shared_ptr<Thing> p1(new Thing);
p1->modify();
// double free
}
std::cout << "end" << std::endl;

return 0;
}

RAII与智能指针
http://helloymf.github.io/2022/12/21/raii-yu-zhi-neng-zhi-zhen/
作者
JNZ
许可协议