共享智能指针

发布时间 2023-12-12 15:07:02作者: Beasts777

文章参考:

爱编程的大丙 (subingwen.cn)

所谓智能指针,其实就是C++11封装的类,里面存有一个正常指针,智能指针会通过这个正常指针,来监视指针指向的内存,当没有智能指针指向该内存时,该内存就被释放。其核心在于引用计数,每一个智能指针指向内存A,智能指针内部的引用计数就加一。每析构一次,就减一。当引用计数为0时,删除指向的堆内存。

C++11提供三种智能指针,头文件为<memory>

  • std::shared_ptr:共享的智能指针。
  • std::unique_ptr:独占的智能指针。
  • std::weak_ptr:若引用的智能指针,它不共享指针,不能操作资源,而是用来监视shared_ptr的。

1. shared_ptr的初始化

共享智能指针是指可以有多个智能指针(普通指针不计数)同时管理同一块有效的内存。shared_ptr是一个模板类,有三种初始化方法:

  • 通过构造函数
  • std::make_shared辅助函数
  • reset方法

如果想要查看有多少智能指针同时管理者某一块内存,可以使用共享智能指针的成员函数use_count,原型如下:

long use_count() const noexcept;

1.1 通过构造函数初始化

#include <iostream>
#include <memory>
using namespace std;

int main(void){
    shared_ptr<int> ptr1(new int(1));
    cout << "ptr1.use_cout()==" << ptr1.use_count() << endl;
    shared_ptr<char> ptr2(new char[3]);
    cout << "ptr2.use_cout()==" << ptr2.use_count() << endl;
    shared_ptr<char[]> ptr3(new char[3]);
    cout << "ptr3.use_cout()==" << ptr3.use_count() << endl;
    shared_ptr<int> ptr4;
    cout << "ptr4.use_cout()==" << ptr4.use_count() << endl;
    shared_ptr<int> ptr5(nullptr);
    cout << "ptr5.use_cout()==" << ptr5.use_count() << endl;
    return 0;
}

输出:

ptr1.use_cout()==1
ptr2.use_cout()==1
ptr3.use_cout()==1
ptr4.use_cout()==0
ptr5.use_cout()==0

结论:

  • 如果智能指针被初始化了一块有效内存,那么该内存的引用计数+1,如果智能指针没有被初始化或者被初始化为nullptr空指针,引用计数不会+1。
  • 不要用一个原始指针初始化多个共享指针,这回导致原始指针的引用计数无法+1。

1.2 通过拷贝构造和移动构造初始化

#include <iostream>
#include <memory>
using namespace std;

int main(void){
    shared_ptr<int> ptr1(new int(1));
    cout << "ptr1.use_cout()==" << ptr1.use_count() << endl;
    shared_ptr<int> ptr2(ptr1);         	// 拷贝构造
    cout << "ptr2.use_cout()==" << ptr2.use_count() << endl;
    shared_ptr<int> ptr3(move(ptr1));       // 移动构造
    cout << "ptr1.use_cout()==" << ptr1.use_count() << endl;
    cout << "ptr3.use_cout()==" << ptr3.use_count() << endl;
    return 0;
}

输出:

ptr1.use_cout()==1
ptr2.use_cout()==2
ptr1.use_cout()==0
ptr3.use_cout()==2

分析:

  • 第9行:通过拷贝构造初始化共享智能指针,ptr1ptr2的引用计数都+1。
  • 第10行:通过移动构造初始化共享智能指针,此时ptr1的管理的内存被转移给ptr3管理,所以ptr1的引用计数归0,而ptr3的引用计数和ptr1原本的引用计数一样。

1.3 通过std::make_shared初始

C++11提供std::make_shared方法,用于完成内存对象的创建,并将创建好的内存对象初始化给智能指针。函数原型如下:

template <class T, class ... Args>
shared_ptr<T> make_shared(Args&&.... args);

EG:

  • 代码:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    class Test {
    private:
        int num;
    public:
        Test() {
            cout << "non-parameter constructor" << endl;
        }
        Test(int n): num(n) {
            cout << "parameterized constructor" << endl;
        }
        ~Test() {
            cout << "destructor" << endl;
        }
        void setValue(int n) {
            num = n;
        }
        void print(){
            cout << "Test.num==" << num << endl;
        }
    };
    
    int main(void){
        shared_ptr<int> ptr1 = make_shared<int>(100);
        cout << "ptr1.use_count()==" << ptr1.use_count() << endl;
        shared_ptr<Test> ptr2 = make_shared<Test>();
        cout << "ptr2.use_count()==" << ptr2.use_count() << endl;
        shared_ptr<Test> ptr3 = make_shared<Test>(100);
        cout << "ptr3.use_count()==" << ptr3.use_count() << endl;
        return 0;
    }
    
  • 输出:

    ptr1.use_count()==1
    non-parameter constructor
    ptr2.use_count()==1
    parameterized constructor
    ptr3.use_count()==1
    destructor
    destructor
    
  • 分析:

    使用std::make_shared()模板函数可以完成内存地址的创建,并将最终得到的内存地址传递给共享智能指针对象管理。且在申请内存的同时,可以调用对应的构造参数对对象进行初始化。

1.5 使用reset方法进行初始化

共享智能指针提供成员函数reset来对自身进行初始化。

原型:

void reset() noexcept;

template <class Y>
void reset(Y* ptr);

template <class Y, class Deleter>
void reset(Y* ptr, Deleter d);

template <class Y, class Deleter, class Alloc>
void reset(Y* ptr, Deleter d, Alloc alloc);

其中:

  • ptr:指向要获取所有权的对象的指针。
  • d:删除器,指向要获取所有权的对象的指针。
  • alloc:内存存储所用的分配器。

作用:

有两种作用:

  • 放弃当前共享智能指针对内存的指向。

    ptr1.reset();
    
  • 转移共享智能指针的指向:

    shared_ptr<int> ptr1(new int(100));
    ptr1.reset(new int(200));			// 
    

EG:

  • 代码:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    int main(void){
        shared_ptr<int> ptr1 = make_shared<int>(100);
        cout << "ptr1.use_count()==" << ptr1.use_count() << endl;
        shared_ptr<int> ptr2 = ptr1;
        cout << "ptr2.use_count()==" << ptr2.use_count() << endl;
        shared_ptr<int> ptr3 = ptr1;
        cout << "ptr3.use_count()==" << ptr3.use_count() << endl;
        cout << endl;
        ptr3.reset();
        cout << "ptr1.use_count()==" << ptr1.use_count() << endl;
        cout << "ptr2.use_count()==" << ptr2.use_count() << endl;
        cout << "ptr3.use_count()==" << ptr3.use_count() << endl;
        cout << endl;
        ptr2.reset(new int(200));
        cout << "ptr1.use_count()==" << ptr1.use_count() << endl;
        cout << "ptr2.use_count()==" << ptr2.use_count() << endl;
        cout << "ptr3.use_count()==" << ptr3.use_count() << endl;
        return 0;
    }
    
  • 输出:

    ptr1.use_count()==1
    ptr2.use_count()==2
    ptr3.use_count()==3
    
    ptr1.use_count()==2
    ptr2.use_count()==2
    ptr3.use_count()==0
    
    ptr1.use_count()==1
    ptr2.use_count()==1
    ptr3.use_count()==0
    

1.5 操作内存

在使用共享智能指针时,如果想要操作指向的目标,有两种方式:

  • 通过成员函数get()方法获取原始指针,用原始指针来操作。get的函数原型如下:

    T* get() const noexcept;
    
  • 把共享智能指针当作原始指针,直接操作。(实际上是在共享智能指针类内部进行了操作符重载)

EG:

  • 代码:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    class Test {
    private:
        int num;
    public:
        Test() {
            cout << "non-parameter constructor" << endl;
        }
        Test(int n): num(n) {
            cout << "parameterized constructor" << endl;
        }
        ~Test() {
            cout << "destructor" << endl;
        }
        void setValue(int n) {
            num = n;
        }
        void print(){
            cout << "Test.num==" << num << endl;
        }
    };
    
    int main(void) {
        shared_ptr<Test> ptr1 = make_shared<Test>(100);
        Test* p = ptr1.get();
        p.print();
        
        ptr1->setValue(200);
        ptr1->print();
        return 0;
    }
    
  • 输出:

    Test.num==100
    Test.num==200
    

2. 指定删除器

2.1 自定义删除器

删除器:

当智能指针管理的内存对应的引用计数为0时,这块内存会被智能指针析构掉。我们可以在初始化智能指针时自己指定删除动作,也就是删除器,其本质上是一个函数。我们只需要实现该函数,器调用由智能指针完成。

EG:

  • 代码:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    void deletor(int *p){
        cout << "custom deletor" << endl;
        delete p;
    }
    
    int main(void) {
        shared_ptr<Test> ptr1(new int(100), deletor);
        return 0;
    }
    
  • 输出:

    custom deletor
    

2.2 lambda 删除器

可以使用lambda表达式作为删除器函数:

#include <iostream>
#include <memory>
using namespace std;

int main(void) {
    shared_ptr<char> ptr(new char('a'),
                         [](char *p){
                             cout << "lambda deletor" << endl;
                             delete p;
                         })
    return 0;
}

输出:

lambda deletor

2.3 数组删除器

在较早版本的C++11中,std::shared_ptr的默认删除器不支持数组对象,如:

class Test {
public:
    Test() { cout << "constructor" << endl; }
    ~Test() { cout << "destructor" << endl; }
};
shared_ptr<Test> ptr(new Test[5]); 

这会导致报错:

constructor
constructor
constructor
constructor
constructor
destructor
段错误 (核心已转储)

明明有五个对象被创建了,却只析构了一个对象,因此内存泄漏,出现段错误。

面对这种问题,有两种解法:

  • 在声明智能指针模板类型时,使用数组

    class Test {
    public:
        Test() { cout << "constructor" << endl; }
        ~Test() { cout << "destructor" << endl; }
    };
    shared_ptr<Test[]> ptr(new Test[5]);	// 这里将模板参数声明为数组类型 
    
  • 定义数组删除器。又有两种方式:

    • 自定义删除器:

      class Test {
      public:
          Test() { cout << "constructor" << endl; }
          ~Test() { cout << "destructor" << endl; }
      };
      shared_ptr<Test> ptr(new Test[5], 
                           [](Test* p){
                               delete[] p;
                           });	
      
    • 使用C++提供的std::default_delete<T>()函数作为删除器。该函数内部逻辑也是通过delete来实现的,要释放什么类型的类型,就指定什么类型的模板参数T

      class Test {
      public:
          Test() { cout << "constructor" << endl; }
          ~Test() { cout << "destructor" << endl; }
      };
      shared_ptr<Test> ptr(new Test[5], default_delete<Test[]>());