智能指针之shared_ptr类
概述
shared_ptr允许多个指针指向同一个对象。它是一个模板,当我们创建shared_ptr时,需要提供额外的信息——指针可以指向的类型。例如:
shared_ptr<string> p1; // 指向string
shared_ptr<list<int>> p2; // 指向int类型的list
默认初始化的shared_ptr中保存一个空指针(nullptr)。智能指针的使用方式与普通指针类似,解引用一个智能指针返回它所指向的对象。总体支持的操作如下表:
操作方法 | 使用说明 |
---|---|
shared_ptr< T > sp | 空只能指针,可以指向类型为T的对象 |
p | 将p作为一个条件判断,若p指向一个对象则返回true |
*p | 解引用p,获得它指向的对象 |
p->member | 等价于(*p).mem |
p.get() | 返回p中保存的指针 |
swap(p, q) | 交换p和q中的指针 |
p.swap(q) | 交换p和q中的指针 |
make_shares< T >(args) | 返回一个shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化这个对象 |
shared_ptr< T > p(q) | p是shared_ptr q的拷贝,此操作会递增q中的计数器,q中的指针必须能转换为T* |
p = q | 此操作会递减q的引用计数,递增p的引用计数 |
p.unique() | 若p.use_count()为1,则返回true;否则返回false |
p.use_count() | 返回与p共享对象的智能指针数量 |
make_shared函数
此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。定义在头文件memory中。
使用make_shared函数时,必须指定想要创建的对象的类型。函数后需要用尖括号指出类型,使用传递的参数来构造给定类型的对象,此参数需要和这个类型的构造函数相匹配。
shared_ptr<int> p1 = make_shared<int>(42); // 指向值为42的int类型
shared_ptr<string> p2 = make_shared<string>(5, '9'); // 指向值为"99999"的string类型
shared_ptr<int> p3 = make_shared<int>(); // 指向值为0的int类型
shared_ptr的拷贝与赋值
当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。无论何时我们拷贝一个shared_ptr,计数器会递增;shared_ptr被赋予一个新值或是shared_ptr被销毁,计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
shared_ptr自动管理所关联的内存
shared_ptr的析构函数会递减它所指向对象的引用计数,如果引用计数变为0,shared_ptr的析构函数会销毁对象,并释放其所占用的内存。例如:
shared_ptr<Foo> factory(T arg) {
return make_shared<Foo>(arg);
}
void useFactory(T arg) {
shared_ptr<Foo> p = factory(arg);
// 当p离开作用域时,它所指向的内存会被自动释放掉
}
shared_ptr与new结合使用
我们可以使用new返回的指针来初始化智能指针:
shared_ptr<double> p(new double(3.14)); // p指向一个值为3.14的double
接收指针参数的智能指针的构造函数是explicit的,因此,必须使用直接初始化形式来初始化一个智能指针:
shared_ptr<int> p1 = new int(1024); // 错误:必须使用直接初始化
shared_ptr<int> p2(new int(1024)); // 正确:使用了直接初始化
p1的初始化隐式地要求编译器用一个new返回的int*类型来创建一个shared_ptr。由于我们不能进行内置指针到智能指针的隐式转换,所以这条语句是错误的。
默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete来释放它所关联的对象。我们也可以将智能指针绑定到一个指向其他类型资源的指针上,但是这样做,必须提供自己的操作来替代delete,定义方法如下表:
定义方法 | 使用说明 |
---|---|
shared_ptr< T > p(q) | p管理内置指针q所指向的对象;q必须指向new分配的内存,且能够转换为T*类型 |
shared_ptr< T > p(u) | p从unique_ptr u那里接管了对象的所有权,并且将u置空 |
shared_ptr< T > p(q, d) | p管理内置指针q所指向的对象,并将可调用对象d来代替delete |
shared_ptr< T > p(p2, d) | p是shared_ptr p2的拷贝,使用可调用对象d来代替delete |
p.reset() | 将p置空,若p是唯一指向其对象的shared_ptr,reset会释放此对象 |
p.reset(q) | 令p指向q |
p.reset(q, d) | 令p指向q,且以后会调用d来释放q |
普通指针与智能指针使用的注意事项
尽量不要混用普通指针与智能指针,例如如下情况则会出现问题:
void process(shared_ptr<int> ptr) {
/****/
}
int main() {
int *x(new int(1024));
process(x); // 错误:不能将int*隐式转换为shared_ptr<int>
process(shared_ptr(x)); // 正确:但是在使用后,内存会被释放
cout << *x << endl; // 未定义的:此时x是空悬指针
return 0;
}
同样也不要使用get初始化另一个智能指针或为智能指针赋值,get函数返回一个内置指针类型,指向智能指针所管理的对象。如果将get返回的指针绑定到另一个新的智能指针上会出现问题:
shared_ptr<int> p(new int(42));
int *q = p.get();
{
shared_ptr<int> temp(q);
}// 程序块结束,temp被销毁,其所指向的内存被释放
cout << *p; // 未定义的:p所指向的内存已经被释放了