C++智能指针(unique_ptr 、shared_ptr、weak_ptr、auto_ptr)

一、概述

智能指针:智能指针是一个类,用来存储指针(指向动态分配对象也就是堆中对象的的指针)。

在实际的 C++ 开发中,我们经常会遇到诸如程序运行中突然崩溃、程序运行所用内存越来越多最终不得不重启等问题,这些问题往往都是内存资源管理不当造成的。比如:

  • 有些内存资源已经被释放,但指向它的指针并没有改变指向(成为了野指针),并且后续还在使用;
  • 有些内存资源已经被释放,后期又试图再释放一次(重复释放同一块内存会导致程序运行崩溃);
  • 没有及时释放不再使用的内存资源,造成内存泄漏,程序占用的内存资源越来越多。

针对以上这些情况,很多程序员认为 C++ 语言应该提供更友好的内存管理机制,这样就可以将精力集中于开发项目的各个功能上。

事实上,显示内存管理的替代方案很早就有了,早在 1959 年前后,就有人提出了“垃圾自动回收”机制。所谓垃圾,指的是那些不再使用或者没有任何指针指向的内存空间,而“回收”则指的是将这些“垃圾”收集起来以便再次利用。

如今,垃圾回收机制已经大行其道,得到了诸多编程语言的支持,例如 Java、Python、C#、PHP 等。而 C++ 虽然从来没有公开得支持过垃圾回收机制,但 C++98/03 标准中,支持使用 auto_ptr 智能指针来实现堆内存的自动回收;C++11 新标准在废弃 auto_ptr 的同时,增添了 unique_ptr、shared_ptr 以及 weak_ptr 这 3 个智能指针来实现堆内存的自动回收。

所谓智能指针,可以从字面上理解为“智能”的指针。具体来讲,智能指针和普通指针的用法是相似的,不同之处在于,智能指针可以在适当时机自动释放分配的内存。也就是说,使用智能指针可以很好地避免“忘记释放内存而导致内存泄漏”问题出现。由此可见,C++ 也逐渐开始支持垃圾回收机制了,尽管目前支持程度还有限。

C++ 智能指针底层是采用引用计数的方式实现的。简单的理解,智能指针在申请堆内存空间的同时,会为其配备一个整形值(初始值为 1),每当有新对象使用此堆内存时,该整形值 +1;反之,每当使用此堆内存的对象被释放时,该整形值减 1。当堆空间对应的整形值为 0 时,即表明不再有对象使用它,该堆空间就会被释放掉。

接下来,我们将分别对unique_ptr 、 shared_ptr以及 weak_ptr 这 3 个智能指针的特性和用法做详细的讲解。

二、unique_ptr

unique_ptr 是auto_ptr的升级版,并且auto_ptr在c++11中已经弃用。

unique_ptr 是一个独享所有权的智能指针:

1、拥有它指向的对象

2、无法进行复制构造,无法进行复制赋值操作。即无法使两个unique_ptr指向同一个对象。但是可以进行移动构造和移动赋值操作

3、保存指向某个对象的指针,当它本身被删除释放的时候,会使用给定的删除器释放它指向的对象

unique_ptr 可以实现如下功能:

1、为动态申请的内存提供异常安全

2、讲动态申请的内存所有权传递给某函数

3、从某个函数返回动态申请内存的所有权

4、在容器中保存指针

5、auto_ptr 应该具有的功能

我们可以通过“.”操作访问指针,通过“->”来访问它指向的对象,shared_ptr 是一样的。

/*
 * 测试类
 */
class String
{
  private:
    char * str; //存储数据
    int len; //字符串长度
    void copyString(const String& a){
        if(this!=&a){
            delete[] str;
            len=a.len;
            str=new char[len+1];
            strcpy(str, a.str);
        }
    }
  public:
    String(){ // 默认构造函数
        len =0;
        str = new char[len+1];
        str[0]='0';
        std::cout << "String() initialize: " << str << std::endl;
    };
    String(const char* s){ //构造函数
        len = strlen(s);
        str = new char[len + 1];
        strcpy(str, s);
        std::cout << "String(char* s) initialize: " << str << std::endl;
    }
    String(const String& a){
        std::cout << "String(String& a) copy initialize: " << str << std::endl;
        copyString(a);
    }
    String& operator=(const String& a)
    {
        std::cout << "operator=(String& a) copy assign: " << str << std::endl;
        copyString(a);
        return *this;
    }
    ~String(){ // 析构函数
        std::cout << "~String() destroy: " << str << std::endl;
        delete[] str;
    };
    friend std::ostream & operator << (std::ostream & os,const String& st){
        os << st.str;
        return os;
    }
};
// shared_ptr测试
String* new_string(){
    return new String("*");  // 谁该负责释放这个S?
};
std::shared_ptr<String> new_string_autoptr(){
    return std::shared_ptr<String>(new String("shared_ptr")); // 显式传递负责释放这个S
}

/*
 * main
 */
int main(int argc, char* argv[]){
    // unique_ptr
    LOG(INFO) << "unique_ptr:" << std::endl;
    std::unique_ptr<String> p1(new String("unique_ptr1"));
    std::unique_ptr<String> p2(new String("unique_ptr2"));
    std::cout << "p1=" << p1 << std::endl;
    std::cout << "p2=" << p2 << std::endl;
    //p1.release();
    //p1.reset();
    p1 = std::move(p2); //move会触发p1原对象的自动销毁,并将p2指向对象指针转移给p1
    std::cout << "p1=" << p1 << std::endl;
    std::cout << "p2=" << p2 << std::endl;
    if (p2 == nullptr){
        std::cout << "unique_ptr2 is nullptr" << std::endl;
    }

    String *p = p1.release();   //p1智能指针计数-1,并将指针转移给p普通指针(p普通指针必须通过delete或托管给智能指针释放)
    std::cout << "p1=" << p1 << std::endl;
    std::cout << "p=" << p << std::endl;

    p2.reset(p);    //p普通指针被p2智能指针托管
    std::cout << "p2=" << p2 << std::endl;
    //std::unique_ptr<String> p3(p);    //ERROR! 同一普通指针不能同时为多个智能指针赋值,会导致析构异常

    std::cout << "-------main end" << std::endl;
    return 0;
}

//运行结果:
I1229 18:07:25.701352 175566272 main.cc:402] unique_ptr:
String(char* s) initialize: unique_ptr1
String(char* s) initialize: unique_ptr2
p1=0x7fcb3f408920
p2=0x7fcb3f408960
~String() destroy: unique_ptr1   //move会触发p1原对象的自动销毁,并将p2指向对象指针转移给p1
p1=0x7fcb3f408960
p2=0x0
unique_ptr2 is nullptr
p1=0x0      //p1智能指针计数-1,并将指针转移给p普通指针
p=0x7fcb3f408960
p2=0x7fcb3f408960   //p普通指针被p2智能指针托管
-------main end
~String() destroy: unique_ptr2   //p2智能指针自动销毁

注意release函数是让指针和指针指向的对象脱离关系,并没销毁。要想销毁,调用reset一个空对象。

三、shared_ptr

从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源(指向的对象)会被释放。此处调动reset不能销毁对象,只有当计数为0时候才可以。

多线程调用它指向的对象的时候,一定要加锁。

/*
 * main
 */
int main(int argc, char* argv[]){
    // shared_ptr
    LOG(INFO) << "shared_ptr:" << std::endl;
    std::shared_ptr<String> strAutoNull;
    std::cout << "strAutoNull.use_count() = " << strAutoNull.use_count() << std::endl;
    std::shared_ptr<String> strAuto(new String("shared_ptr A"));
    //std::shared_ptr<String> strAuto = std::shared_ptr<String>(new String("shared_ptr A"));   //shared_ptr不用显式删除
    //std::shared_ptr<String> strAuto = new_string_autoptr();
    std::shared_ptr<String> strAuto2(strAuto);
    std::shared_ptr<String> strAuto3 = strAuto;
    strAuto = new_string_autoptr();
    std::cout << "strAuto.use_count() = " << strAuto.use_count() << std::endl;
    std::cout << "strAuto2.use_count() = " << strAuto2.use_count() << std::endl;
    strAuto.reset();    //strAuto.reset()后引用计数为0,触发自动销毁
    strAuto3.reset();   //strAuto2引用计数-1,但仍>0
    std::cout << "strAuto.use_count() = " << strAuto.use_count() << std::endl;
    std::cout << "strAuto2.use_count() = " << strAuto2.use_count() << std::endl;

    String *pp = new String("pp");   //智能指针计数-1,并将指针转移给p(p普通指针必须通过delete或托管给智能指针释放)
    std::unique_ptr<String> pa2(pp); 
    //std::unique_ptr<String> pa3(pp);    //ERROR! 同一普通指针不能同时为多个智能指针赋值,会导致析构异常

    //std::shared_ptr<String> pssl(new String[2]);    //数组智能指针不能自动销毁
    std::shared_ptr<String> pssl(new String[2], std::default_delete<String[]>()); //数组智能指针需要添加一个删除器,这里使用default_delete<T> 模板类
    //std::shared_ptr<String> pssl(new String[2], [](String* p) {delete[]p; }); //数组智能指针需要添加一个删除器,这里使用lambd表达式
    std::cout << "-------main end" << std::endl;
    return 0;
}

//运行结果:
I1229 19:56:33.937842 321351104 main.cc:427] shared_ptr:
strAutoNull.use_count() = 0
String(char* s) initialize: shared_ptr A
String(char* s) initialize: shared_ptr
strAuto.use_count() = 1
strAuto2.use_count() = 2
~String() destroy: shared_ptr
strAuto.use_count() = 0
strAuto2.use_count() = 1
String(char* s) initialize: pp
String() initialize: 0
String() initialize: 0
-------main end
~String() destroy: 0
~String() destroy: 0
~String() destroy: pp
~String() destroy: shared_ptr A

四、week_ptr

weak_ptr可以用来解决shared_ptr相互引用时的死锁问题,如果两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。weak_ptr也可以用于多线程想使用对象,但是并不管理对象的场景。

weak_ptr没有重载 * 和 -> ,所以并不能直接使用资源。但可以使用lock()获得一个可用的shared_ptr对象,
如果对象已经死了,lock()会失败,返回一个空的shared_ptr。

int main(int argc, char* argv[]){
    // weak_ptr
    LOG(INFO) << "weak_ptr:" << std::endl;
    std::shared_ptr<int> sp1(new int(10));
    std::shared_ptr<int> sp2(sp1);
    std::weak_ptr<int> wp(sp2); // weak_ptr没有重载* 和 -> ,无法直接当指针用
    std::cout << "wp.use_count() = " << wp.use_count() << std::endl;   //输出和wp同指向的shared_ptr类型指针的数量
    sp2.reset();    //释放 sp2
    std::cout << "wp.use_count() = " << wp.use_count() << std::endl;
    std::cout << "*(wp.lock()) = " << *(wp.lock()) << std::endl;    //通过lock()函数,返回一个和wp同指向的shared_ptr类型指针,并通过*获取其存储的数据
    std::cout << "*(wp.lock()) = " << *(wp.lock()) << std::endl;    //使用lock获取的原因

    std::cout << "-------main end" << std::endl;
    return 0;
}

//执行结果
I1229 20:04:01.980633 200826304 main.cc:453] weak_ptr:
wp.use_count() = 2
wp.use_count() = 1
*(wp.lock()) = 10
*(wp.lock()) = 10
参考:
http://c.biancheng.net/view/7898.html
https://blog.csdn.net/maosijunzi/article/details/80772122

欢迎关注下方“非著名资深码农“公众号进行交流~

发表评论

邮箱地址不会被公开。