0%

C++ STL中的智能指针

参考文章:auto_ptr, unique_ptr, shared_ptr and weak_ptr
参考文章:auto_ptr, unique_ptr, shared_ptr and weak_ptr智能指针讲解

C++ 库提供以下类型的智能指针实现:

  • auto_ptr
  • unique_ptr
  • shared_ptr
  • weak_ptr

头文件和命名空间:

1
2
#include <memory>
using namespace std;

auto_ptr

模板 auto_ptr 是 C++98 提供的解决方案,从 C++11 开始,此类模板被弃用。 unique_ptr 是具有相似功能但具有改进的安全性的新工具。

auto_ptr 是一个智能指针,用于管理通过 new 表达式获取的对象,并在 auto_ptr 本身被销毁时删除该对象。

使用 auto_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
#include <chat_global.hpp>
#include <memory>

class A
{
public:
A()
{
PRINT_INFO(__func__);
}

~A()
{
PRINT_INFO(__func__);
}
};

int main(int argc, char const *argv[])
{
/* code */
PRINT_INFO("----------auto_ptr----------");
{
// pa1 is an auto_ptr of type A
std::auto_ptr<A> pa1(new A);
//or
//A* pa = new A;
//std::auto_ptr<A> pa1(pa);

//error: conversion from ‘A*’ to non-scalar type ‘std::auto_ptr<A>’ requested
//std::auto_ptr<A> pa1 = pa;
PRINT_INFO("pa1 address: " << pa1.get());

// this makes pa1 empty
std::auto_ptr<A> pa2(pa1);
//or
//std::auto_ptr<A> pa2 = pa1;
// pa1 is empty now
PRINT_INFO("pa1 address: " << pa1.get());
PRINT_INFO("pa2 address: " << pa2.get());

// Aborted (core dumped)
//A* pa = new A;
//std::auto_ptr<A> pa3(pa);
//std::auto_ptr<A> pa4(pa);

// Aborted (core dumped)
// auto_ptr的内部实现中,析构函数中删除对象使用delete而不是delete[],所以auto_ptr不能用来管理数组指针
//std::auto_ptr<A> pa3(new A[3]);
}

return 0;
}

输出:

[INFO]: ----------auto_ptr----------
[INFO]: A
[INFO]: pa1 address: 0x21f7010
[INFO]: pa1 address: 0
[INFO]: pa2 address: 0x21f7010
[INFO]: ~A 

auto_ptr 的拷贝构造函数和赋值运算符实际上并没有复制存储的指针,而是将它们传输出去,是第一个 auto_ptr 对象变为空。这是实现严格所有权的一种方法,因此只有一个 auto_ptr 对象可以在任何给定时间拥有该指针,即在需要复制语义的情况下不应使用 auto_ptr。

auto_ptr 被弃用原因:

独占所有权的方式获取指针的所有权。赋值转移所有权并将 之前的 auto_ptr 对象重置为空指针,可导致出现悬空指针,存在潜在的内存崩溃风险。因此,由于上述无法复制,它们不能在STL容器内使用


unique_ptr

unique_ptr 是在 C++11 中开发的,可替代 auto_ptr
unique_ptr 是一个具有类似功能的新的智能指针模板,具有更高的安全性(无伪造复制分配),添加删除器功能和数组支持。它是一个原始指针的容器,它明确的防止复制其包含的指针,只允许底层指针的一个所有者,因此,当使用 unique_ptr 时,在任何一个资源上最多只能有一个 unique_ptr 。

由于任何资源只能有一个 unique_ptr,因此任何尝试复制 unique_ptr 都会导致编译时错误:

1
2
3
4
5
6
7
8
9
10
std::unique_ptr<A> pa1(new A);
PRINT_INFO("pa1 address: " << pa1.get());

/*error: use of deleted function
‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&)
[with _Tp = A; _Dp = std::default_delete<A>]’*/

//无法复制
//std::unique_ptr<A> pa2(pa1);
//std::unique_ptr<A> pa2 = pa1;

unique_ptr 可以使用新的语义,即使用 std::move() 函数将包含的指针的所有权转移到另一个 unique_ptr。

1
2
3
4
5
6
7
std::unique_ptr<A> pa1(new A);
PRINT_INFO("pa1 address: " << pa1.get());

// resource now stored in pa2
std::unique_ptr<A> pa2 = std::move(pa1);
PRINT_INFO("pa1 address: " << pa1.get());
PRINT_INFO("pa2 address: " << pa2.get());

输出:

[INFO]: A
[INFO]: pa1 address: 0x23c2010
[INFO]: pa1 address: 0
[INFO]: pa2 address: 0x23c2010
[INFO]: ~A

下面的代码返回一个资源,如果我们没有显式捕获返回值,那么资源将被清除。如果我们这样做,那么我们拥有该资源的独家所有权。通过这种方式,我们可以将 unique_ptr 视为更安全,更好的 auto_ptr 替代品。

1
2
3
4
std::unique_ptr<A> make_A()
{
return std::unique_ptr<A>(new A);
}

std::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
//默认删除器
{
auto std_deleter = std::default_delete<A>();
std::unique_ptr<A, decltype(std_deleter)> pa4(new A, std_deleter);
}

//lambda删除器
{
auto deleter = [](A *pa){delete pa;};
std::unique_ptr<A, decltype(deleter)> pa4(new A, deleter);

auto deleter_array = [](A* pArr){delete []pArr;};
std::unique_ptr<A, decltype(deleter_array)> pa5(new A[2], deleter_array);
}

//函数指针删除器
{
typedef void (*deleter)(A*);

std::unique_ptr<A, deleter> pa4(new A, func_deleter);
}

//仿函数删除器
{
struct deleter
{
void operator()(A *pa)
{
delete pa;
}
};

std::unique_ptr<A, deleter> pa4(new A, deleter());
}

释放 unique_ptr 所有权:

1
2
3
4
5
6
7
8
std::unique_ptr<A> pa1(new A);

//release pa1
A *pa = pa1.get();
pa1.release();
PRINT_INFO("pa1 address: " << pa1.get());
delete pa;
pa = NULL;

输出:

[INFO]: A
[INFO]: pa1 address: 0
[INFO]: ~A

最好使用 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
PRINT_INFO("---------unique_ptr---------");
{
std::unique_ptr<A> pa1(new A);
PRINT_INFO("pa1 address: " << pa1.get());

/*error: use of deleted function
‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&)
[with _Tp = A; _Dp = std::default_delete<A>]’*/
//std::unique_ptr<A> pa2(pa1);
//std::unique_ptr<A> pa2 = pa1;

PRINT_INFO("pa1 address: " << pa1.get());
// PRINT_INFO("pa2 address: " << pa2.get());

// resource now stored in pa2
std::unique_ptr<A> pa2 = std::move(pa1);
PRINT_INFO("pa1 address: " << pa1.get());
PRINT_INFO("pa2 address: " << pa2.get());

std::unique_ptr<A> pa3 = make_A();
PRINT_INFO("pa3 address: " << pa3.get());

//release pa3
A *pa = pa3.get();
pa3.release();
PRINT_INFO("pa3 address: " << pa3.get());
delete pa;
pa = NULL;
}

输出:

[INFO]: ---------unique_ptr---------
[INFO]: A
[INFO]: pa1 address: 0x23c2010
[INFO]: pa1 address: 0x23c2010
[INFO]: pa1 address: 0
[INFO]: pa2 address: 0x23c2010
[INFO]: A
[INFO]: pa3 address: 0x23c2030
[INFO]: pa3 address: 0
[INFO]: ~A
[INFO]: ~A

何时使用 unique_ptr
当您想拥有资源的唯一所有权(Exclusive)时,请使用 unique_ptr, 只有一个 unique_ptr 可以指向一个资源, 因为单个资源可以有一个 unique_ptr,所以不可能将一个 unique_ptr 复制到另一个。

shared_ptr

shared_ptr 是原始指针的容器。 它是一个引用计数模型,即它与shared_ptr 的所有副本合作维护其包含的指针的引用计数。 因此,每当一个新的指针指向资源时,计数器就会递增,当调用对象的析构函数时递减计数器。

引用计数:计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。

引用计数大于0,所包含的原始指针引用的对象将不会被销毁,直到所有的 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
PRINT_INFO("---------shared_ptr---------");
{
std::shared_ptr<A> pa1(new A);
PRINT_INFO("pa1 address: " << pa1.get());

std::shared_ptr<A> pa2(pa1);
std::shared_ptr<A> pa3 = pa1;

PRINT_INFO("pa1 address: " << pa1.get());
PRINT_INFO("pa2 address: " << pa2.get());
PRINT_INFO("pa3 address: " << pa3.get());

// Returns the number of shared_ptr objects
//referring to the same managed object.
PRINT_INFO("use count: " << pa1.use_count());
PRINT_INFO("use count: " << pa2.use_count());
PRINT_INFO("use count: " << pa3.use_count());

// Relinquishes ownership of p1 on the object
//and pointer becomes NULL
pa1.reset();

PRINT_INFO("pa1 address: " << pa1.get());
PRINT_INFO("pa2 address: " << pa2.get());
PRINT_INFO("pa3 address: " << pa3.get());

PRINT_INFO("use count: " << pa1.use_count());
PRINT_INFO("use count: " << pa2.use_count());
PRINT_INFO("use count: " << pa3.use_count());
}

输出:

[INFO]: ---------shared_ptr---------
[INFO]: A
[INFO]: pa1 address: 0x254f010
[INFO]: pa1 address: 0x254f010
[INFO]: pa2 address: 0x254f010
[INFO]: pa3 address: 0x254f010
[INFO]: use count: 3
[INFO]: use count: 3
[INFO]: use count: 3
[INFO]: pa1 address: 0
[INFO]: pa2 address: 0x254f010
[INFO]: pa3 address: 0x254f010
[INFO]: use count: 0
[INFO]: use count: 2
[INFO]: use count: 2
[INFO]: ~A

std::shared_ptr 设置删除器的方法:

1
std::shared_ptr<A> pa(new A[3], [](A* pa){delete []pa;});

只有用一个 shared_ptr 为另一个 shared_ptr 赋值时,才将这连个共享指针关联起来,直接使用地址值会导致各个 shared_ptr 独立。

1
2
3
4
5
6
7
8
9
10
A *pa = new A;
std::shared_ptr<A> pa1(pa);
std::shared_ptr<A> pa2 = pa1;
// error
std::shared_ptr<A> pa3(pa1.get());
//or
//std::shared_ptr<A> pa4(pa);
PRINT_INFO("pa1 use count: " << pa1.use_count());
PRINT_INFO("pa2 use count: " << pa2.use_count());
PRINT_INFO("pa3 use count: " << pa3.use_count());

输出:

[INFO]: pa1 use count: 2
[INFO]: pa2 use count: 2
[INFO]: pa3 use count: 1

何时使用 shared_ptr
如果要共享资源的所有权,请使用 shared_ptr。许多 shared_ptr 可以指向单个资源。 shared_ptr 维护资源的引用计数。当所有指向资源的shared_ptr超出范围时,资源将被销毁。
share_ptr 完美支持标准容器,并且不需要担心资源泄漏。

weak_ptr

std::weak_ptr 是一种智能指针,它对被 std::shared_ptr 管理的对象存在非拥有性(“弱”)引用(不参与引用计数),weak_ptr 的存在或破坏对 shared_ptr 或其他副本没有影响,在访问所引用的对象前必须先转换为 std::shared_ptr

在某些情况下需要中断 shared_ptr 实例之间的循环引用,防止内存泄露。

循环依赖( shared_ptr 的问题):让我们考虑一个有两个类 A 和 B 的场景,A 中包含 B 的指针变量,B 中包含 A 的指针变量。有如下所示关系时:

use_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
32
33
34
35
36
PRINT_INFO("----------weak_ptr----------");
{
struct A;
struct B;

struct A
{
A(){ PRINT_INFO("struct A()"); }
~A(){ PRINT_INFO("struct ~A()"); }

std::shared_ptr<B> _pb;
};

struct B
{
B(){ PRINT_INFO("struct B()"); }
~B(){ PRINT_INFO("struct ~B()"); }

std::shared_ptr<A> _pa;
};

std::shared_ptr<A> pa(new A);// = std::make_shared<A>();
std::shared_ptr<B> pb(new B);// = std::make_shared<B>();

PRINT_INFO("pa use_count: " << pa.use_count());
PRINT_INFO("pb use_count: " << pb.use_count());

//A->B B->A
//memory leak
pa->_pb = pb;
pb->_pa = pa;

PRINT_INFO("pa use_count: " << pa.use_count());
PRINT_INFO("pb use_count: " << pb.use_count());
}

输出(内存泄露):

[INFO]: ----------weak_ptr----------
[INFO]: struct A()
[INFO]: struct B()
[INFO]: pa use_count: 1
[INFO]: pb use_count: 1
[INFO]: pa use_count: 3
[INFO]: pb use_count: 3

这就是我们使用弱指针(weak_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
PRINT_INFO("----------weak_ptr----------");
{
struct A;
struct B;

struct A
{
A(){ PRINT_INFO("struct A()"); }
~A(){ PRINT_INFO("struct ~A()"); }

std::weak_ptr<B> _pb;
};

struct B
{
B(){ PRINT_INFO("struct B()"); }
~B(){ PRINT_INFO("struct ~B()"); }

std::weak_ptr<A> _pa;
};

//use (new A); (new B); A have memory leak ??
std::shared_ptr<A> pa = std::make_shared<A>();
std::shared_ptr<B> pb = std::make_shared<B>();

PRINT_INFO("pa use_count: " << pa.use_count());
PRINT_INFO("pb use_count: " << pb.use_count());

//A->B B->A
pa->_pb = pb;
pb->_pa = pa;

PRINT_INFO("pa use_count: " << pa.use_count());
PRINT_INFO("pb use_count: " << pb.use_count());
}

输出:

[INFO]: ----------weak_ptr----------
[INFO]: struct A()
[INFO]: struct B()
[INFO]: pa use_count: 1
[INFO]: pb use_count: 1
[INFO]: pa use_count: 1
[INFO]: pb use_count: 1
[INFO]: struct ~B()
[INFO]: struct ~A()
1
2
3
4
5
6
7
8
9
10
11
std::shared_ptr<A> pa(new A);
std::weak_ptr<A> pwa = pa;

PRINT_INFO("pwa use_count: " << pwa.use_count());

// lock return shared_ptr
std::shared_ptr<A> pa1 = pwa.lock();
if (pa1 != NULL)
{
pa1->Show();
}

输出:

[INFO]: A
[INFO]: pwa use_count: 1
[INFO]: Show
[INFO]: ~A

std::weak_ptr 用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用 std::weak_ptr 来跟踪该对象。需要获得临时所有权时,则将其转换为 std::shared_ptr,此时如果原来的 std::shared_ptr 被销毁,则该对象的生命期将被延长至这个临时的 std::shared_ptr 同样被销毁为止。

std::weak_ptr 的另一用法是打断 std::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
//reset 
{
PRINT_INFO("---------------auto_ptr reset---------------");
{
std::auto_ptr<A> pa1(new A);
pa1.reset();
PRINT_INFO("reset() pa1 end");
pa1.reset(new A);
PRINT_INFO("reset(new A) pa1 end");
}

PRINT_INFO("--------------unique_ptr reset--------------");
{
std::unique_ptr<A> pa1(new A);
pa1.reset();
PRINT_INFO("reset() pa1 end");
pa1.reset(new A);
PRINT_INFO("reset(new A) pa1 end");
}

PRINT_INFO("--------------shared_ptr reset--------------");
{
std::shared_ptr<A> pa1 = std::make_shared<A>();
std::shared_ptr<A> pa2 = pa1;

PRINT_INFO("use count:" << pa2.use_count());
pa1.reset(new A);//use count --
PRINT_INFO("use count:" << pa2.use_count());
}

PRINT_INFO("---------------weak_ptr reset---------------");
{
std::shared_ptr<A> pa1(new A);
std::weak_ptr<A> pa = pa1;

PRINT_INFO("use count: " << pa1.use_count());
pa.reset();
PRINT_INFO("use count: " << pa1.use_count());
}

}

输出:

[INFO]: ---------------auto_ptr reset---------------
[INFO]: A
[INFO]: ~A
[INFO]: reset() pa1 end
[INFO]: A
[INFO]: reset(new A) pa1 end
[INFO]: ~A
[INFO]: --------------unique_ptr reset--------------
[INFO]: A
[INFO]: ~A
[INFO]: reset() pa1 end
[INFO]: A
[INFO]: reset(new A) pa1 end
[INFO]: ~A
[INFO]: --------------shared_ptr reset--------------
[INFO]: A
[INFO]: use count:2
[INFO]: A
[INFO]: use count:1
[INFO]: ~A
[INFO]: ~A
[INFO]: ---------------weak_ptr reset---------------
[INFO]: A
[INFO]: use count: 1
[INFO]: use count: 1
[INFO]: ~A