文章目录
  1. 1. 什么是智能指针
  2. 2. 智能指针的原理
  3. 3. shared_ptr的用法
    1. 3.1. 构造shared_ptr
    2. 3.2. 判断智能指针是否为空
    3. 3.3. 判断智能指针是否相等
    4. 3.4. 通过*引用智能指针
    5. 3.5. 通过get引用智能指针
    6. 3.6. 通过->引用智能指针的成员
  4. 4. weak_ptr的用法
  5. 5. unique_ptr的用法
  6. 6. 智能指针的使用场景
    1. 6.1. 观察者模式
    2. 6.2. 工厂模式
    3. 6.3. 避免代码发生异常时的内存泄露
  7. 7. 是否所有使用场景都可以用智能指针代替普通指针

转载请注明 作者:源码先生, 文章链接:https://www.debugself.com/2017/09/24/cpp_shared_ptr/, 请勿用于商业用途

什么是智能指针

智能指针,是一种特殊的指针,它可以自动释放new出来的指针,不需要程序员手动调用delete即可释放;

智能指针的原理

智能指针是一个包装类,内部包装了真正的数据指针(即new出来的内存地址)和一个引用计数。

当构造智能指针时(即智能指针的构造函数或者复制构造函数被调用时),引用计数会加1;

当析构智能指针时(即析构函数被调用时)引用计数会减1,并判断引用计数是否为0,为0时调用delete删除真正的数据指针;

智能指针重载了->操作符,当调用智能指针的->时,内部会转换为真正的数据指针的解引用;所以智能指针使用起来,和使用普通指针基本一致。

C++11的定义了多种智能指针,他们都包含在#include 头文件中

shared_ptr的用法

从名字上看shared_ptr是共享指针,意味着我们可以复制shared_ptr,复制出的智能指针指向同一个内部数据指针(即被智能指针包装的真正数据)。

构造shared_ptr

有多种方法可以构造shared_ptr,下面代码中有4种构造方式:

1
2
3
4
5
6
int *p = new int(1);
shared\_ptr<int> sp1(p);// 通过普通指针p构造shared\_ptr
shared_ptr<int> sp2 = sp1;// 复制,sp2也指向p
shared\_ptr<int> sp3 = make\_shared<int>(2);// 通过make\_shared构造,make\_shared内部调用了new int(2);
sp3.reset(new int(3));// 通过reset重置内部数据指针
sp3.reset();// 通过reset重置内部数据指针为空

注意,不能构造两个独立的智能指针,且指向同一个内部数据指针

1
2
3
int *p = new int(1);
shared_ptr<int> sp1(p);
shared_ptr<int> sp2(p);

上述代码中,p最终会被释放两次,从而造成错误!当需要sp2也包装p时,请使用sp2=sp1;

判断智能指针是否为空

可以直接用if、!操作符判断智能指针是否为空

1
2
3
4
5
6
7
8
9
10
11
shared_ptr<int> sp;
if(!sp){
    cout<<"sp is null"<<endl;
}
sp.reset(new int(0));
if(sp){
    cout<<"sp is not null"<<endl;
}
//输出结果
sp is null
sp is not null

判断智能指针是否相等

直接用==操作符判断智能指针是否相等,因为智能指针类内部重载了==操作符

1
2
3
4
5
6
7
8
9
10
shared\_ptr<int> sp1=make\_shared<int>(3);
auto sp2 = sp1;
if(sp2 == sp1){
    cout<<"true"<<endl;
}
else{
    cout<<"false"<<endl;
}
//输出结果
true

通过*引用智能指针

智能指针重载了操作,通过可以获取智能指针的内部数据指针

1
2
3
4
shared\_ptr<int> sp1 = make\_shared<int>(1);
cout<<*sp1<<endl;
//输出结果
1

通过get引用智能指针

get返回了智能指针的内部数据的指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Foo{
public:
    Foo(int i,int j){
        this->i=i;
        this->j=j;
    }
    void print(){cout<<i<<j<<endl;}
private:
    int i;
    int j;
};
shared\_ptr<Foo> sp2 = make\_shared<Foo>(1,2);
Foo *pf = sp2.get();
pf->print();
//输出结果
12

通过->引用智能指针的成员

智能指针重载了->操作,通过->可以直接引用智能指针的内部数据指针

1
2
3
4
shared\_ptr<Foo> sp2 = make\_shared<Foo>(1,2);
sp2->print();
//输出结果
12

注意,上述有多种引用智能指针的方式,其中的区别如下:

  • 调用shared_ptr类本身的函数时,用.操作符,如sp2.reset();
  • 调用shared_ptr内部数据的函数时,用->操作符,如sp2->print();

use_count()获取有多少个智能指针共享同一个内部数据指针

1
2
3
4
5
6
7
shared\_ptr<int> sp1 = make\_shared<int>(1);
cout<<sp1.use_count()<<endl;
auto sp2 = sp1;
cout<<sp1.use_count()<<endl;
//输出结果
1
2

weak_ptr的用法

从名字上看,weak_ptr是弱指针,即它比shared_ptr要弱一点。weak_ptr可以看做shared_ptr的助手,weak_ptr要和shared_ptr配套一起使用。当创建一个weak_ptr时,要用一个shared_ptr来初始化它。

我们知道,复制shared_ptr是会增加内部数据的引用计数,但是复制weak_ptr时,以及由shared_ptr构造weak_ptr时,是不会增加引用计数的;且weak_ptr没有重载、->操作符,所以不能通过、->操作符操作智能指针的内部数据,这就是weak_ptr弱的原因吧,汗。

因为weak_pt不增加引用计数,我们可以任意构造weak_ptr,任意释放weak_ptr,却不会影响智能指针中内部数据的释放(内部数据何时释放,只取决于shared_ptr)。那么weak_ptr有什么用呢?weak_ptr可以用来监看shared_ptr:

  • weak_ptr::use_count()查看有多少个shared_ptr共享同一个内部数据
  • weak_ptr::expired判断shared_ptr是否有效,即shared_ptr内部数据是否被释放

weak_ptr是否可以监看shared_ptr中的内部数据呢?因为weak_ptr是弱指针,所以不能直接访问,但是可以通过weak_ptr::lock间接访问。

1
weak_ptr::lock

weak_ptr::lock返回构造weak_ptr的shared_ptr,当shared_ptr已经被释放时,返回的是空shared_ptr;注意,因为weak_ptr::lock返回了shared_ptr,而shared_ptr会增加引用计数,进而影响内部数据指针的释放,这也是lock的含义所在,想通过weak_ptr访问shared_ptr中的内部数据,需要先lock,返回一个shared_ptr,这相当于把weak_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
int main()   
{   
    std::weak_ptr<int> wp0;   
    std::cout << "wp0.expired() == " << std::boolalpha   
        << wp0.expired() << std::endl;   
  
    std::shared_ptr<int> sp1(new int(5));   
    std::weak_ptr<int> wp1(sp1);   
    std::cout << "*wp1.lock() == "   
        << *wp1.lock() << std::endl;   
  
    std::weak_ptr<int> wp2(wp1);   
    std::cout << "*wp2.lock() == "   
        << *wp2.lock() << std::endl;   
  
    return (0);   
 }
// 输出结果
wp0.expired() == true  
*wp1.lock() == 5  
*wp2.lock() == 5

unique_ptr的用法

unique_ptr是一个独占的智能指针,即unique_ptr不支持复制,但是支持通过move转移内部指针

1
2
3
unique_ptr<T> myPtr(new T);      // ok
unique_ptr<T> otherPtr = myPtr;    // 编译错误
unique_ptr<T> otherPtr = move(myPtr);    // ok

同shared_ptr,unique_ptr也不能多个unique_ptr指向同一个内部数据指针;

1
2
3
int *p = new int(0);
unique_ptr<int> up1(p);
unique_ptr<int> up2(p);// 错误,up2和up1都指向了p,将来p会被释放两次,造成错误

智能指针的使用场景

分享几种工作中使用到智能指针的场景。

观察者模式

现在有一个设备模块,该模块从设备获取一条条数据(这里用Record表示一条数据),并通过观察者模式把Record分发给所有(观察了该数据的)观察者。观察者一般都是视图,如视图A得到数据后,通过表格显示数据的内容,而视图B上得到数据后,通过趋势图显示数据的内容。

当用户关闭了所有视图,意味没有视图再使用数据Record了,这时就可以释放掉Record;但是只要有一个视图在使用record,就不能释放Record。

如何管理Record的释放呢?这种情况使用智能指针,可以做到所有视图关闭后,自动释放Record。

工厂模式

假如你编写了一个工厂类,它提供一个接口,根据配置产生各种对象(内部调用new新建对象)。

由于该类很NewBee,它被封装为动态库提供给其他同事,当其他同事调用动态库得到新建对象后,新建对象将来由谁负责释放呢?如果没有统一且明确的沟通确认,很容易出现双方都忘记释放新建对象,或者同一个新建对象被双方都释放了一次的情况!

这时候你甩出了一个智能指针,然后宣布:大家都不用关心谁来释放了,让指针自己释放去吧。

避免代码发生异常时的内存泄露

如下的代码:

1
2
3
4
5
6
7
8
9
void foo(){
try{
    int *p = new int(0);
    do_something(p);
    delete p;
}
catch(...){
}
}

如果do_something抛出异常,delete p是不会被执行的,从而造成内存泄露。使用智能指针的话,即使do_something发生异常,在栈上申请的局部变量依旧会被销毁,当指针指针被销毁时,它的析构函数会自动释放内存。

是否所有使用场景都可以用智能指针代替普通指针

非也,比如某个时刻,你发现程序内存占用非常大,你想手动delete释放之前申请的内存空间,如果你使用了智能指针,因为智能指针的释放是依赖智能指针的析构函数,但是我们又不能手动调用析构函数,这意味着你无法手动释放智能指针,这种场景中,就需要使用普通指针。