手动管理的弊端
在简单的程序中,我们不大可能忘记释放 new 出来的指针,但是随着程序规模的增大,我们忘了 delete 的概率也随之增大。在 C++ 中 new 出来的指针,赋值意味着引用的传递,当赋值运算符同时展现出“值拷贝”和“引用传递”两种截然不同的语义时,就很容易导致“内存泄漏”。
手动管理内存带来的更严重的问题是,内存究竟要由谁来分配和释放呢?指针的赋值将同一对象的引用散播到程序各处,但是该对象的释放却只能发生一次。当在代码中用完了一个资源指针,该不该释放 delete 掉它?这个资源极有可能同时被多个对象拥有着,而这些对象中的任何一个都有可能在之后使用该资源,其余指向这个对象的指针就变成了“野指针”;那如果不 delete 呢?也许你就是这个资源指针的唯一使用者,如果你用完不 delete,内存就泄漏了。
资源的拥有者是系统,当我们需要时便向系统申请资源,当我们不需要时就让系统自己收回去(Garbage Collection)。当我们自己处理的时候,就容易出现各种各样的问题。
C++ 中的智能指针
为了让用户免去手动 delete 资源的烦恼,不少类库采用了 RAII 风格,即 Resource Acquisition Is Initialization(资源获取即初始化)。这种风格采用类来封装资源,在类的构造函数中获取资源,在类的析构中释放资源,这个资源可以是内存,可以是一个网络连接。
智能指针就是 C++ RAII 的一种应用,是存储指向动态分配对象指针的类。智能指针在面对异常的时候格外有用,因为它们能在适当的时间删除所指向的对象(可以了解一下异常安全)。C++ 中的智能指针首先出现在 boost 中,随着使用的人越来越多,C++11 也已经引入了智能指针来管理动态对象。C++11 主要提供了 shared_ptr、unique_ptr、weak_ptr 三种不同类型的智能指针。
shared_ptr
智能指针是(几乎总是)模板类,shared_ptr 同样是模板类,所以在创建 shared_ptr 时需要指定其指向的类型。shared_ptr 负责在不使用实例时释放由它管理的对象,同时它可以自由的共享它指向的对象。
shared_ptr 使用经典的“引用计数”的方法来管理对象资源。引用计数指的是,所有管理同一个裸指针( raw pointer )的 shared_ptr,都共享一个引用计数器,每当一个 shared_ptr 被赋值(或拷贝构造)给其它 shared_ptr 时,这个共享的引用计数器就加1,当一个 shared_ptr 析构或者被用于管理其它裸指针时,这个引用计数器就减1,如果此时发现引用计数器为0,那么说明它是管理这个指针的最后一个 shared_ptr 了,于是我们释放指针指向的资源。
在底层实现中,这个引用计数器保存在某个内部类型里(这个类型中还包含了 deleter,它控制了指针的释放策略,默认情况下就是普通的delete操作),而这个内部类型对象在 shared_ptr 第一次构造时以指针的形式保存在 shared_ptr 中(所以一个智能指针的析构会影响到其他指向同一位置的智能指针)。shared_ptr 重载了赋值运算符,在赋值和拷贝构造另一个 shared_ptr 时,这个指针被另一个 shared_ptr 共享。在引用计数归零时,这个内部类型指针与 shared_ptr 管理的资源一起被释放。此外,为了保证线程安全性,引用计数器的加1,减1操作都是原子操作,它保证 shared_ptr 由多个线程共享时不会爆掉。
对于 shared_ptr 在拷贝和赋值时的行为,《C++Primer第五版》中有详细的描述:
每个 shared_ptr 都有一个关联的计数值,通常称为引用计数。无论何时我们拷贝一个 shared_ptr,计数器都会递增。
例如,当用一个 shared_ptr 初始化另一个 shred_ptr,或将它当做参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。当我们给 shared_ptr 赋予一个新值或是 shared_ptr 被销毁(例如一个局部的 shared_ptr 离开其作用域)时,计数器就会递减。一旦一个 shared_ptr 的计数器变为0,它就会自动释放自己所管理的对象。
下面看一个常见用法,包括:
1. 创建 shared_ptr 实例 2. 访问所指对象 3. 拷贝和赋值操作 4. 检查引用计数。更多的用法请自行查阅。
/*
* @filename: shared_ptr.cpp
* @author: Tanswer
* @date: 2018年01月10日 19:40:45
* @description:
*/
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Test{
public:
Test(string name){
name_ = name;
cout << this->name_ << " constructor" << endl;
}
~Test(){
cout << this->name_ << " destructor" << endl;
}
string name_;
};
int main()
{