shared_ptr在内部维护一个相当于引用计数的机制,允许多个指针同时指向一个对象。某个指针被销毁之后,引用计数同时较少,当所有指针都被销毁之后,自动释放管理的对象。

本文主要内容仍然参照:参考文献

首先,我们需要知道:智能指针的出现是为了便于程序员对内存进行管理而引入的。为了解决这个问题,C++11标准库引入了三种智能指针,动作类似于常规指针,同时具备在合适的时机自动释放内存的功能(防止内存泄漏)。其次,我们应该理解:智能指针背后的设计思想,其实是将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。

本文先主要介绍智能指针之一——shared_ptr,一种采用引用计数机制的智能指针

1. 预备工作

为了更好地展示智能指针shared_ptr的用法及作用,我们首先构造一个类:

class MyString
{
private:
    string text;

public:
    MyString(string str) : text(str)
    {
        cout << "Constructed (" << str << ")!" << endl;
    }
    ~MyString()
    {
        cout << "Destructed (" << text << ")!" << endl;
    }

    void Output()
    {
        cout << text << endl;
    }
};

每当这个类的对象被创建和销毁的时候,可以相应的输出Constructed和Destructed字符串,这样我们可以清晰地观察为1变量分配内存和释放内存的时间。

2. shared_ptr的声明、初始化、赋值

首先,我们给出一个简单的实例。由于shared_ptr是一个模版类,在声明时必须声明指向对象的类型:

shared_ptr<MyString> p1;                //定义了一个空的shared_ptr,未初始化

shared_ptr<MyString> p2=make_shared<MyString>("Hello ptr ->");  //初始化,最安全的方式是使用make_shared标准库函数
p1 = p2;                        //赋值

之所以说make_shared这种方式安全,是因为make_shared虽然也生成了MyString对象,并将这个对象直接装配到shared_ptr上。

需要强调的是,shared_prt的本身是一个类,所以它的初始化实际上就是调用shared_ptr类的构造函数。通过分析shared_ptr的源码,我们可以发现以下构造函数:
1)default构造函数

_LIBCPP_CONSTEXPR shared_ptr() _NOEXCEPT;
//default构造函数:e.g: shared_ptr<int> p;  ———— 声明空shared_ptr

2)空指针构造

_LIBCPP_CONSTEXPR shared_ptr(nullptr_t) _NOEXCEPT;
//由空指针构造:e.g: shared_ptr<int> p(nullptr); ———— 通过空指针构造shared_ptr

3)根据已有指针构造shared_ptr

explicit shared_ptr (U* p);
//根据已有指针构造shared_ptr:
//shared_ptr<string> p1(new string("First"));    ———— 对象创建后直接构造shared_ptr

//string *s=new string("Second");
//shared_ptr<string>  p2(s);              ———— 对象创建后先临时保存,再构造shared_ptr

根据已有指针构造shared_ptr的两种方式都合法,但是在第二种情况中应该理解,一旦构造了share_ptr,就表明s指向的string对象的内存管理已经交给ps1管理而不应该在通过s访问了(至少是非常小心的访问)。所以,第二种方式应该尽量避免。

4)拷贝构造

shared_ptr(const shared_ptr& __r) _NOEXCEPT;
//拷贝构造:
//shared_ptr<string> ps1(new strinng("Test")); ———— ps1.use_count()=1;
//shared_ptr<string> ps2(ps1);  ———— ps1.use_count() = ps2.use_count()=2;  ps1.get()=ps2.get();

拷贝构造函数执行以后,引用计数增加,两个指针指向同一个对象。

5)移动构造

shared_ptr(shared_ptr&& __r) _NOEXCEPT;
//移动构造:由一个已有的shared_ptr创建新的share_ptr。作为参数的shared_pt同时会释放对内存的管理权利,整个构造过程结束后,引用计数不变。
//shared_ptr<string> ps1(new strinng("Test")); ———— ps1.use_count()=1;
//shared_ptr<string> ps2(move(ps1));  ———— ps1.use_count()=0; ps2.use_count()=1; ps1.get()=0; ps2.get()=****

移动构造中,作为参数的shared_pt同时会释放对内存的管理权利,整个构造过程结束后,引用计数不变。

此外,对于赋值操作,也有以下几种方式:
1)拷贝赋值:从已有指针赋值给shared_ptr,共享数据管理权。(类似于拷贝构造)

shared_ptr& operator= (const shared_ptr& x) noexcept;


2)移动赋值:从已有指针赋值给shared_ptr,伴随数据管理权的转移。移动赋值和拷贝赋值的区别在于参数是否为右值引用。

shared_ptr& operator= (shared_ptr&& x) noexcept;



需要注意的是:make_shared也属于移动赋值。

3. shared_ptr的使用

可以像普通指针一样使用shared_ptr:

p1->Output();
p2->Output();

示例代码:

#include <iostream>
#include <memory>
#include <string>

using namespace std;

class MyString
{
private:
    string text;

public:
    MyString(string str) : text(str)
    {
        cout << "Constructed (" << str << ")!" << endl;
    }
    ~MyString()
    {
        cout << "Destructed (" << text << ")!" << endl;
    }

    void Output()
    {
        cout << text << endl;
    }
};

int main()
{
    MyString mystring("Hello world :-)");
    mystring.Output();

    shared_ptr<MyString> p1;
    //p1->Output();

    shared_ptr<MyString> p2=make_shared<MyString>("Hello ptr ->");
    //p2->Output();

    p1=p2;

    p1->Output();
    p2->Output();

    return 0;
}

/*输出
Constructed (Hello world :-))!
Hello world :-)
Constructed (Hello ptr ->)!
Hello ptr ->
Hello ptr ->
Destructed (Hello ptr ->)!
Destructed (Hello world :-))!
*/

不难发现,代码中没有使用new和delete,但是仍然可以像指针一样使用shared_ptr,而且不需要担心内存泄漏。

4. 小结

shared_ptr的核心思想正如其名称一样——共享,即采用计数机制。为了更好的演示其计数机制,引用知乎上的一个例子:

int main(){
    //局部作用域
    {
        shared_ptr<string> p1(new string("hello world"));
        //引用计数=1,即只有一个指针p1引用这块string内存

        {
            shared_ptr<string> p2=p1;
            //引用计数=2,此时有p1、p2两个指针1可以访问这块内存
        }
        //引用计数=1,超出p2指针作用域,仅p1可访问
    }
    //引用计数=0,超出p1作用域,没有指针可以访问,资源被释放。
    return 0;
}

具体可参考以下代码:

#include <iostream>
#include <memory>
#include <string>

using namespace std;

class MyString
{
private:
    string text;

public:
    MyString(string str) : text(str)
    {
        cout << "Constructed (" << str << ")!" << endl;
    }
    ~MyString()
    {
        cout << "Destructed (" << text << ")!" << endl;
    }

    void Output()
    {
        cout << text << endl;
    }
};

int main()
{
    int flag1=6,flag2=15;

    if (flag1>5)
    {
        shared_ptr<MyString> p1=make_shared<MyString>("Hello");
        cout<<"p1.use_count = "<<p1.use_count()<<endl;
        cout<<"p1.get = "<<p1.get()<<endl;

        cout<<"=========="<<endl;
        if (flag2>10)
        {
            shared_ptr<MyString> p2=p1;

            cout<<"p1.use_count = "<<p1.use_count()<<endl;
            cout<<"p2.use_count = "<<p2.use_count()<<endl;

            cout<<"p1.get = "<<p1.get()<<endl;
            cout<<"p2.get = "<<p2.get()<<endl;
        }
        cout<<"=========="<<endl;
        cout<<"p1.use_count = "<<p1.use_count()<<endl;
        cout<<"p1.get = "<<p1.get()<<endl;
        
    }
    

    return 0;
}

/*
输出:
Constructed (Hello)!
p1.use_count = 1
p1.get = 0x7f948cc05a18
==========
p1.use_count = 2
p2.use_count = 2
p1.get = 0x7f948cc05a18
p2.get = 0x7f948cc05a18
==========
p1.use_count = 1
p1.get = 0x7f948cc05a18
Destructed (Hello)!
*/