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所指对象在拷贝数据之间进行共享。
说明:
-
返回值和形参应该尽量设置为
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