智能指针之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所指向的内存已经被释放了
posted @ 2020-03-20 20:24  southernEast  阅读(684)  评论(0编辑  收藏  举报