C++智能指针总结:shared_ptr

在程序运行时分配的内存空间是需要在运行中释放的,这部分内存称之为堆。

智能指针不用自己释放内存,只要没有指针指向内存了,就会自动释放。下面是两种智能指针:

  • shared_ptr允许多个指针指向同一个对象。使用一个计数器记录对象被多少指针指向。无论何时我们拷贝一个shared_ptr,计数器都会递增。例如,当用一个 shared_ ptr 初始化另一个shared_ ptr, 或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数 器就会递增。当我们给shared_ ptr赋予一个新值或是shared_ ptr被销毁(例如一个 局部的shared_ ptr 离开其作用域时),计数器就会递减。 一旦一个shared_ _ptr 的计数器变为0,它就会自动释放自己所管理的对象。
  • unique_ptr则“独占”所指向的对象。
  • 标准库还定义了一个名为weak_ ptr的伴随类,它是一种弱引用,指向shared ptr所管理的对象。这三种类型都定义在memory头文件中。

1. shared_ptr

shared_ptr是一个智能指针支持普通指针的很多操作,如解引用*p
shared_ptr是一个类,所以shared_ptr对象肯定有一些可调用的方法,如empty()方法:

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

int main(int argc, char* argv[]) {
    // 创建一个空string
    std::shared_ptr<std::string> p1 = std::make_shared<std::string>();
    // std::shared_ptr<std::string> p1 = std::make_shared<std::string>(""); // 与上面语句等价
    // auto p1 = std::make_shared<std::string>(""); 
    if(p1 && p1->empty()){
        *p1 = "hello";
        std::cout << *p1 << std::endl;
    }

    return 0;
}
  • p1 && p1->empty()代表p1指向某个对象,且p1所指对象为空。
  • make_shared用于动态分配内存并初始化。常常使用auto定义make_shared生成的结果。

再如reset()方法:删除管理对象,接收新指针。

// shared_ptr::reset example
#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> sp;  // empty

  sp.reset (new int);       // takes ownership of pointer
  *sp=10;
  std::cout << *sp << '\n';

  sp.reset (new int);       // deletes managed object, acquires new pointer
  *sp=20;
  std::cout << *sp << '\n';

  sp.reset();               // deletes managed object

  return 0;
}

程序使用动态内存出于以下三种原因之一:
1.程序不知道自己需要使用多少对象
2.程序不知道所需对象的准确类型
3.程序需要在多个对象间共享数据

1.1 程序需要在多个对象间共享数据:

定义一个类,类的数据成员为一个 shared_ptr。使用此 shared_ptr 来管理一个 vector,即可实现在多个类对象间共享同一个 vector。当所有类对象都被销毁时 vector 才会被销毁。【注意】一个类只会与它的拷贝共享一个 vector,单独定义的两个类是不共享的。
一个实例:StrBlob类:
StrBlob 类是一个使用动态内存在多个对象间共享内存的例子。
StrBlob 类中仅有一个 shared_ptr 成员,这个 shared_ptr 指向一个 string 的 vector。

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <exception>

using std::vector; using std::string;

class StrBlob {
public:
    using size_type = vector<string>::size_type;      // 灵活使用类型别名

    StrBlob():data(std::make_shared<vector<string>>()) { }
    StrBlob(std::initializer_list<string> il):data(std::make_shared<vector<string>>(il)) { }  //定义了一个接受初始化列表的转换构造函数(注意不是 explicit 的)

    size_type size() const { return data->size(); }   // size() 函数不改变数据成员,所以声明为 const 的
    bool empty() const { return data->empty(); }      // 声明为 const 的

    void push_back(const string &t) { data->push_back(t); }
    void pop_back() {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }

    std::string& front() {
        check(0, "front on empty StrBlob");
        return data->front();
    }

    std::string& back() {
        check(0, "back on empty StrBlob");
        return data->back();
    }

    const std::string& front() const {       //在普通的 front() 函数外又重载了一个 const 的版本
        check(0, "front on empty StrBlob");
        return data->front();
    }
    const std::string& back() const {       //在普通的 back() 函数外又重载了一个 const 的版本
        check(0, "back on empty StrBlob");
        return data->back();
    }

private:
    void check(size_type i, const string &msg) const {   //定义了一个 check 函数来检查索引是否超出边界
        if (i >= data->size()) throw std::out_of_range(msg);  //不检查 i 是否小于 0 是因为 i 的类型是 size_type,是无符号类型,如果 i<0 会被自动转换为大于 0 的数
    }

private:
    std::shared_ptr<vector<string>> data;
};

StrBlob类中只有一个shared_ptr类型的成员data,当StrBlob类对象被拷贝时,引用计数就会增加。这就实现了data所指对象在拷贝数据之间进行共享。
说明:

  • C++ STL 容器的size_type

  • 返回值和形参应该尽量设置为const&。如代码中const std::string&作为返回值的原因为:const保证了data智能指针不会再外部被修改,&减少了一次拷贝操作。

  • const成员函数中不能修改成员变量,但是可以修改成员指针所指向对象。如const std::string& front() const中修改data所指对象,但是不能修改data本身。

  • std::out_of_range(msg);为一个越界异常。

  • 这里的接受 initializer_list 的转换构造函数没有定义为 explicit 的,这样的好处是使用方便,可以进行隐式的转换。缺点是不易调试。

接下来的内容请直接参考第12章 C++ primer

posted @ 2023-03-17 22:16  好人~  阅读(123)  评论(0编辑  收藏  举报