价值投资 长期主义 编程 美食 旅行 梦想 参禅 悟道

0%

智能指针shared_ptr

勿在浮沙筑高台

本文的目标是掌握清楚 智能指针 shared_ptr 、unique_ptr 和weak_ptr 的用法, 涉及到的概念和理念。

概念

RAII(Resource Acquisition Is Initialization)

RAII 含义即是资源分配即初始化。

  • 讲的是这样一个理念,将资源的管理放在一个类中,利用类的生命周期构造函数和析构函数来进行资源的管理和释放。用局部对象来表示资源。

  • 这是c++ 编程中的最重要编程技法之一。

  • 智能指针便是利用 RAII 的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现却像一个指针

C++11 中提供了三种智能指针

分别是 shared_ptr , unique_ptr 和 weak_ptr 。

  1. shared_ptr 允许多个指针指向同一个对象,
  2. unique_ptr 则“独占”所指向的对象,
  3. weak_ptr 则是和share_ptr 相辅相成的伴随类

shared_ptr

share_ptr是一个类,它产生的是一个类对象,而不是一个原生的指针对象,但是为了减少类对象与针对对象使用的差异性,所以share_ptr类故意重载了两种常见的指针操作符: *和->。从而share_ptr与普通指针使用方式一样。简言之,就是share_ptr生成的一个包含类型指针容器对象,它封装了指针对象,对指针对象负全责,包括生成、释放等;

  1. shared_ptr多个智能指针可以指向相同对象;
  2. 能够自动释放所指向的对象
  3. 该对象和其相关资源会在“最后一个引用被销毁”时候释放。
  4. 对象创建完就应该直接交给智能指针管理;
  5. shared_ptr 采用引用计数器,多个shared_ptr种的 T*ptr 指向同一内存区域(同一对象),并共同维护同一个引用计数器。
  6. 初始化指针并将引用计数置为1;
  7. 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数。
  8. 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;这是因为左侧的指针指向了右侧指针所指向的对象,因此右指针所指向的对象的引用计数加1。调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
  9. share_ptr完美支持标准容器,并且不需要担心资源泄漏。而标准容易在使用指针对象时需要特别的小心,对指针需要额外的管理 。
  10. 特别需要注意的是,share_ptr的转型函数不能使用c++常用的转型函数,即static_cast,dynamic_cast,const_cast,而要使用static_pointer_cast,dynamic_pointer_cast,const_pointer_cast。原因有两个:static_cast,dynamic_cast,const_cast的功能是转换成对应的模版类型,即static_cast<T*>其实是转换成类型为T的指针;前面说了share_ptr生成的一个包含类型指针容器对象,使用简单的c++转型函数是将share_ptr对象转型为模版指针对象,这完全违背了使用share_ptr的初衷(除非你确确实实有这种需要!),导致转型的模版指针对象不能采用share_ptr进行管理。因为上面的两个原因:share_ptr为了支持转型,所以提供了类似的转型函数即static_pointer_cast,从而使转型后仍然为shared_pointer对象,仍然对指针进行管理;

Member functions

  • 构造函数 不谈拷贝构造的话,shared_ptr的基本构造方式有4种:

    • 无参数构造。
    • 传入一个指针构造一个shared_ptr
    • 传入一个指针和一个删除器构造一个shared_ptr。
    • 传入一个指针、一个删除器以及一个allocator构造一个shared_ptr。
    • 当然还有一些其他的,例如从auto_ptr从weak_ptr从null_ptr构造。
  • 析构函数 用来释放智能指针,至于是否释放智能指针所管理的对象,取决于成员use_count的值;

    • 如果use_count > 1,则引用计数器减一;
    • 如果use_count=1,则引用计数器置零,资源释放。
    • 如果use_count=0,则说明此智能指针本来就没有指向对象。
  • operator=

    赋值操作后,赋值前原来的 _refCount要自减 , 计数器_refCount要做++ ;

  • swap

    交换,使两个智能指针分别指向对方的对象。

  • reset 复位操作,不再指向对象,原来所指向对象的_refCount要自减

  • get 获取对象的原始指针。

  • operator* 相当于*get()

  • operator-> 可以像原指针一样,指向原指针的成员。

  • use_count 返回当前所指对象的引用计数。

  • unique 检查Usecount() == 1

  • [operator bool](http://www.cplusplus.com/reference/memory/shared_ptr/operator bool/) 检查 get()!=NULL

  • owner_before

    Owner-based ordering (public member function template )

Non-member functions

多线程问题

shared_ptr的线程安全的定义在boost的文档中有明确的说明:

  • 一个shared_ptr对象可以被多个线程同时read

  • 两个shared_ptr对象,指向同一个raw指针,两个线程分别write这两个shared_ptr对象,是安全的。包括析构。

  • 多个线程如果要对同一个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
#include <iostream>
#include <memory>

struct C {int* data;};

int main () {
std::shared_ptr<int> p1;
std::shared_ptr<int> p2 (nullptr);
std::shared_ptr<int> p3 (new int);
std::shared_ptr<int> p4 (new int, std::default_delete<int>());
std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>());
std::shared_ptr<int> p6 (p5);
std::shared_ptr<int> p7 (std::move(p6));
std::shared_ptr<int> p8 (std::unique_ptr<int>(new int));
std::shared_ptr<C> obj (new C);
std::shared_ptr<int> p9 (obj, obj->data);

std::cout << "use_count:\n";
std::cout << "p1: " << p1.use_count() << '\n';
std::cout << "p2: " << p2.use_count() << '\n';
std::cout << "p3: " << p3.use_count() << '\n';
std::cout << "p4: " << p4.use_count() << '\n';
std::cout << "p5: " << p5.use_count() << '\n';
std::cout << "p6: " << p6.use_count() << '\n';
std::cout << "p7: " << p7.use_count() << '\n';
std::cout << "p8: " << p8.use_count() << '\n';
std::cout << "p9: " << p9.use_count() << '\n';

std::shared_ptr<int> foo = std::make_shared<int> (10);
// same as:
std::shared_ptr<int> foo2 (new int(10));

auto bar = std::make_shared<int> (20);

auto baz = std::make_shared<std::pair<int,int>> (30,40);

std::cout << "*foo: " << *foo << '\n';
std::cout << "*bar: " << *bar << '\n';
std::cout << "*baz: " << baz->first << ' ' << baz->second << '\n'
return 0;
}

std::weak_ptr

引入是为了解决 std::share_ptr 的链接环问题。c++

一般结合强智能指针使用,它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段;weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 不会引起引用记数的增加或减少。

std::unique_ptr

unique_ptr 不共享它的指针。它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL) 算法。只能移动

手册

  1. http://www.cplusplus.com/reference/memory/shared_ptr/
  2. http://c.biancheng.net/view/430.html

后续

智能指针shared_ptr的实现

主要的问题

  1. shared_ptr 整体是如何实现的;
  2. shared_ptr 计数器的实现;
  3. shared_ptr 怎么实现-> 操作,访问所指向对象的成员就像访问本身的成员一样。
万水千山总是情,打赏一块行不行!