【学习总结】boost智能指针

一、为什么使用智能指针

      为管理内存等资源,C++采取RAII机制(资源获取即初始化,Resource Acquisition Is Initialization),在使用资源的类的构造函数中申请资源并使用,最终在析构函数中释放资源。使用new在堆上创建对象时,其析构函数不会自动调用,需要使用delete才能释放资源,若因为异常导致程序未能执行delete,则存在内存泄露的问题。C++98标准中的“自动指针std::auto_ptr(C++11中废弃,改用unique_ptr)部分解决了获取资源自动释放的问题。

二、boost库提供的智能指针

        boost.smart_ptr库提供六种智能指针,包括socped_ptr、scoped_array、shared_ptr、shared_array、weak_ptr和intrusive_ptr。他们都是轻量级的对象,速度与原始指针相差无几,都是异常安全的,而且对于所指向的类型T也仅有一个很小且合理的要求:类型T的析构函数不能抛出异常。

      头文件:#include

scoped_ptr 保证智能指针只能在本作用域中使用,拥有对象的唯一所有权,不可以复制。如果一个类中有scoped_ptr成员变量,则该类也不可拷贝或赋值。
scoped_array 类似scoped_ptr,只不过包装的是new[ ]操作符,不推荐使用
shared_ptr 实现了引用计数,可以拷贝或赋值,当引用计数为0时自动删除动态分配的对象
shared_array 类似shared_ptr,但包装的是new[ ]分配的动态数组
weak_ptr 配合shared_ptr而引入,不具备普通指针的行为,作为shared_ptr拥有对象的非拥有观察者
intrusive_ptr 引用计数型智能指针,可以包装已有对象得到与shar_ptr类似的智能指针

1. socped_ptr

       scoped_ptr包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除,scoped_ptr获取对象管理权后就不能再转让。

       scoped_ptr的构造函数接受一个类型为T*的指针p,创建出一个scoped_ptr对象,并在内部保存指针参数p。p必须是new表达式动态分配的结果,或者是一个空指针(nullptr)。当scoped_ptr对象的生命期结束时,析构函数~scoped_ptr()会使用delete操作符自动销毁所保存的指针对象来正确回收资源。拷贝构造函数和赋值操作符被声明为私有的,禁止对智能指针的拷贝操作,保证了被它管理的指针不能被转让所有权。

       scoped_ptr提供在bool语境(如if的条件表达式)中自动转换成bool值的功能,用来测试scoped_ptr是否持有一个有效的指针(非空),可以替代与空指针的比较操作(有限比较,仅能与空指针进行比较操作)。

#include
using namespace boost;

struct posix_file{    //一个示范性质的文件类
    posix_file(const char *file_name){
        cout << "open file: " << file_name << endl;
    }

    ~posix_file(){
        cout << "close file" << endl;
    }
}

int main(){
    //文件类的scoped_ptr,将在离开作用域时自动析构,从而关闭文件释放资源
    scopoed_ptr fp(new posix_file("/tmp/a.txt"));

    scoped_ptr p(new int);    //一个int指针的scoped_ptr
    
    if(p){    //在bool语境中测试指针是否有效
        *p = 100;    //可以像普通指针一样使用解引用操作符*
        cout << *P <

2. scoped_array

       包装了new[ ]操作符在堆上分配的动态数组,为动态数组提供了一个代理,来保证正确释放内存。主要特点:

  • 构造函数接受的指针p必须是new[ ]的结果,而不是new表达式的结果;
  • 没有*、->操作符重载,因为scoped_array持有的不是一个普通指针;
  • 析构函数使用delete[ ]操作重载,而不是delete;
  • 提供operator操作符重载,可以想普通数组一样用下标访问元素;
  • 没有begin()、end()等类似容器的迭代器操作函数。
#include
using namespace boost;

int main(){
    int *arr = new int[100];    //一个整数的动态数组
    scoped_array sa(arr);    //scoped_array对象代理原始动态数组

    fill_n(&sa[0],100,5);    //可以使用标准库算法赋值数据
    sa[10] = sa[20] + sa[30];    //用起来就像是个普通数组
}    //这里scoped_array被自动析构,释放动态数组资源

3. shared_ptr

        shared_ptr是最像指针的”智能指针“,是boost.smart_ptr库中最有价值、最重要、最有用的组成部分。他包装了new操作符在堆上分配的动态对象,实现了引用计数,可以自由地拷贝和赋值,在任意地方进行共享。也可以安全地放到标准容器中,是在STL容器中储存指针的最标准解法。

    shared_ptr有多种形式的构造函数,应用于可能的情形:

  • 无参的shared_ptr()创建一个持有空指针的shared_ptr;
  • shared_ptr(Y *p)获得指向类型T的指针p的管理权,同时引用计数置为1。这个构造函数要求Y类型必须能够装换为T类型;
  • shared_ptr(shared_ptr const &r)从另外一个shared_ptr获得指针的管理权,同时引用计数加1,结果是两个shared_ptr共享一个指针的管理权;
  • operator=赋值操作符可以从另外一个shared_ptr获取指针的管理权,其行为等同构造函数;
  • shared_ptr(Y *p, D d)行为类似于shared_ptr(Y *p),但使用参数d指定了析构时的定制删除器,而不是简单的delete;
  • 别名构造函数(aliasing),不增加引用计数的特殊用法。

     reset()函数将shared_ptr的引用计数减1,停止对指针的共享,除非引用计数为0,否则不会发生删除操作;带参数的reset()类似相同形式的构造函数,原引用计数减1的同时改为管理另一个指针。

     unique()use_count()专门用来检查引用计数,unique()在指针是唯一所有者时返回true,是可靠的,任何时候都可用;use_count()返回当前指针的引用计数,不提供高效率的操作。

     shared_ptr支持比较运算,可以测试两个shared_ptr的相等或不相等,比较基于内部保存的指针,相当于a.get() == b.get();还可以使用operator<比较大小,但不提供除此外的比较操作符。

     此外shared_ptr还支持流输出操作符operator<< ,输出内部的指针值,方便调试。

class shared{    //一个拥有shared_ptr的类
private:
    shared_ptr p;    //shared_ptr成员变量
public:
    shared(shared_ptr p_):p(p_){    //构造函数初始化shared_ptr
    
    }
    void print(){    //输出shared_ptr的引用计数和指向的值
    cout << "count: " << p.use_count()
         << "v = " << *p < p){    //使用shared_ptr作为函数参数
    cout << "count: " << p.use_count()    //同样输出引用计数和指向的值
         << "v = " << *p < p(new int(100))
    shared s1(p),s2(p);    //构造两个自定义类

    s1.print();
    s2.print();

    *p = 20;    //修改shared_ptr所指的值
    print_func(p);
    s1.print();
}

        工厂函数make_shared()可以接受若干参数,然后把它们传递给类型T的构造函数,创建一个shared_ptr对象并返回。这要比直接创建shared_ptr对象的方式快且高效,因为内部仅分配了一次内存,消除了shared_ptr构造时的开销。allocate_shared()与make_shared()相同,但多一个定制的内存分配器类型参数。

auto sp = make_shared("make_shared");    //创建string的共享指针
auto spv = make_shared>(10,2);    //创建vector的共享指针

         shared_ptr应用于标准容器、应用于桥接模式、应用于工厂函数、定制删除器.......

4. shared_array

shared_array与shared_ptr基本相同,具有shared_ptr的优点和scoped_array的缺点,主要区别:

  • 构造函数接受的指针p是new[ ] 的结果;
  • 提供operator[ ] 操作符重载,可以像数组一样用下标访问元素;
  • 没有*、->操作符重载;
  • 析构函数使用delete [ ] 释放资源,而不是delete。
#include
using namespace boost;

int main{
    int *p = new [100];    //一个动态数组
    shared_array sa(p);    //shared_array代理动态数组
    assert(sa.unique());    //唯一持有指针

    shared_array sa2 = sa;    //共享数组,引用计数增加
    assert(sa2.use_count()==2);    //引用计数增加

    sa[0] = 10;    //可以使用operator[]访问元素
    assert(sa2[0] == 10);
}    //离开作用域,自动删除动态数组

      shared_array不提供数组索引的范围检查,如果使用超过动态数组大小的索引或负索引将引发未定义行为。

5. weak_ptr

         weak_ptr被设计与shared_ptr协同工作,可以从一个shared_ptr或另一个weak_ptr对象构造,获得资源的观察权,构造和析构不会引起指针引用计数的增加或减少。

          use_count()可以观测资源的引用计数,expired()的功能等价于use_count()==0,但更快,表示被观测的资源不复存在。lock() 从被观测的shared_ptr获得一个可用的shared_ptr对象,把弱关系转换为强关系,从而操作资源,当expired()==true时,lock()返回空指针的shared_ptr。

enable_shared_from_this

           weak_ptr一个重要用途是获取this指针的shared_ptr,使对象能够自己生产shared_ptr管理自己:对象使用weak_ptr观测this指针,这并不影响引用计数,需要时调用lock()函数,返回一个符合要求的shared_ptr供外界使用。成员函数shared_from_this()会返回this的shared_ptr。使用方法:

class self_shared : 
      public enable_shared_from_this{

pulic:
    self_shared(int n):x(n){
    
    }

    int x;
    void print(){
        cout << "self_shared: " << x << endl;
    }
};

int main(){
    auto sp = make_shared(313);
    sp -> print();
    
    auto p = sp ->shared_from_this();    //返回this的shared_ptr
    
    p -> x = 1000;
    p -> print();
}

enable_shared_from_raw

           与enable_shared_from_this类似,但不要求对象必须被一个shared_ptr管理,可以直接从一个原始指针创建出shared_ptr。

#include

class raw_shared : public boost::enable_shared_from_raw{
public:
    raw_shared(){
        cout << "raw_shared ctor" << endl;
    }
    ~raw_shared(){
        cout << "raw_shared dtor" << endl;
    }
};

int main(){
    raw_shared x;    //一个普通对象
    asser(!weak_from_raw(&x).use_count());    //此时无引用,注意要用&取地址

    auto px = shared_from_raw(&x);    //获取shared_ptr
    assert(px.use_count()==2);    //引用计数为2!
}    //对象自动删除

循环引用

          代码中出现“循环引用”时,shared_ptr的引用机制会失效,导致不能正确析构释放资源。使用weak_ptr在可能存在循环引用的地方打破循环,在需要shared_ptr的时候调用weak_ptr的lock()函数。

class node{    //链表节点的类
public:
    ...
    typeder weak_ptr ptr_type;    //指针类型使用weak_ptr
    ptr_type next;    //后继指针
};

int main(){
    auto p1 = make_shared();    //两个节点对象
    auto p2 = make_shared();

    p1 -> next = p2;    //形成循环链表
    p2 -> next = p1;    //引用使用了weak_ptr所以正常

    assert(p1.use_count() == 1);    //每个shared_ptr的引用计数是1
    assert(p2.use_count() == 1);    //没有了循环引用

    if(!p1 -> next.expired()){    //检查弱引用是否有效
        auto p3 = p1 -> next.lock();    //调用lock()获得强引用
    }
}    //退出作用域,shared_ptr均正确析构

6. intrusive_ptr

        intrusive_ptr接口与shared_ptr很像,同样支持比较和static_pointer_cast()、dynamic_pointer_cast()等转型操作,但不直接管理引用计数,而是调用函数来间接管理:

void intrusive_ptr_add_ref(T *p);    //增加引用计数
void intrusive_ptr_release(T *p);    //减少引用计数

intrusive_ptr构造函数和reset()多出一个bool add_ref参数,表示是否增加引用计数,如果add_ref == true,那么它就相当于weak_ptr,只是简单地观察对象。

参考文档:

BOOST程序库完全开发指南:深入C++“准”标准库

如何理解智能指针? 知乎

C++智能指针简单剖析

注:

assert是C/C++提供的准确性验证、测试支持的宏,详见 BOOST程序库完全开发指南第6章:正确性与测试

你可能感兴趣的:(个人学习)