C++:智能指针之shared_ptr

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sixdaycoder/article/details/45787713

1.智能指针

C++中用new来动态分配内存,delete手动释放内存来达到动态管理内存的目的。因为保证在正确的时间释放内存是非常困难的,忘记释放内存就会产生内存泄露。

为了更安全、便捷的使用动态内存,C++11标准库提供了新的智能指针类来管理动态内存。智能指针在行为上和普通的指针是一样的,只不过它可以保证内存在适当的时候自动释放。 
新的标准库提供了两种智能指针(在头文件<memory>中),区别在于管理底层指针的方式。

  • shared_ptr允许多个指针指向同一个对象。
  • unique_ptr“独占”指向的对象。

本文仅介绍shared_ptr的相关用法。


2.创建shared_ptr智能指针

类似vector,智能指针也是一种模板,因此创建智能指针时,我们需要提供额外的信息表明指针指向的类型。 
shared_ptr<int> p1;//创建一个空指针p1,可以指向int类型 
shared_ptr<list<int>> p2;//创建一个空指针p2,可以指向list<int>类型 
这仅仅是创建了一个智能指针,并没有给它分配内存。 
通常有两种方式给shared_ptr分配内存

make_shared函数分配内存

最安全的分配和使用动态内部的方式是调用名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。

shared_ptr<int> p1 = make_shared<int>(42);
//创建指向int的shard_ptr并且用42初始化
shared_ptr<string> p2 = make_shared<string>(10, '9');
//创建一个指向string的shared_ptr并且调用string的(n,char)构造函数初始化
shared_ptr<my_class> p3 = make_shared<my_class>();
//创建指向my_class的shared_ptr并且调用my_class的默认构造函数初始化

类似于容器操作的emplace,make_shared函数用其参数来构造给定类型的对象。所以说如果是构造一个类的shared_ptr,一定要注意初始化时必须和某个构造函数匹配

前边提到过,shared_ptr允许多个指针指向同一个对象。所以我们可以认为每一个shared_ptr都有一个和它关联的计数器,称为引用计数器。当满足下述条件时,引用计数器递增。

  • 初始化shared_ptr: shared_ptr<int> p1 = make_shared<int>(42);此时p1指向的对象只有p1一个引用者,引用计数为1

  • 拷贝shared_ptr:shared_ptr<int> q(p);此时q和p指向同一个对象,该对象的引用计数为2

  • 将shared_ptr当做一个函数的参数传递:

shared_ptr<int> p1 = make_shared<int>(42);
printCount(p1);
void printCount(shared_ptr<int> p)
{
    cout << p.use_count() << endl;//返回p指向对象的引用计数
}

输出结果是2,因为p1被当做了参数传递

  • 将shared_ptr用作函数的返回值
shared_ptr<vector<int>> getVector()
{
    return make_shared<vector<int>>();
}
shared_ptr<vector<int>> p = getVector();
cout << p.use_count() << endl;

输出结果是1,虽然初始化的时候没有显式的用make_shared函数给p分配内存,但是调用了getVector()函数,返回了一个shard_ptr,这样p指向的对象引用计数器增加。

当一个对象的引用计数减为0后,shared_ptr会自动销毁对象,释放内存。

当以下两种情况发生时,shared_ptr的引用计数会递减

  • 给shared_ptr赋予新值:
auto p1 = make_shared<int>(42);
p1 = p2;

给p1赋值使它指向p2指向的对象,会递增p2指向的对象的引用计数,递减p1指向对象的引用计数,此时p1指向对象的引用计数为0,被自动销毁。

  • 局部shared_ptr离开其作用域:
void printCount(shared_ptr<int> p)
{
    shared_ptr<int> x = make_shared<int>();
    cout << x.use_count() << endl;
}

x是局部shared_ptr,当函数结束时,x指向的对象引用计数减为0,x指向的对象被销毁,释放占用的内存。

shared_ptr与new结合使用创建智能指针

如前所说,如果不初始化一个智能指针,那么它就会被初始化为一个空指针。除了用make_shared函数我们还可以用new返回指针初始化智能指针。 
例如shared_ptr<int> p(new int(42)); 
该语句实际上调用了智能指针的构造函数,该构造函数的参数是普通指针。这种构造函数是显式的,因此shared_ptr<int> p = new int(42);这种方式是错误的。 
同样的理由,一个返回shared_ptr的函数不能在其返回语句中隐式转换一个普通指针。

shared_ptr<int> clone(int p)
{
    return new int(p);
    //错误,内置指针不能被隐式的转换为shared_ptr
}

正确写法应当是:

shared_ptr<int> clone(int p)
{
    return shared_ptr<int> (new int(p));
} 

还有,必须要强调的一点是: 
默认情况下,一个用来初始化shared_ptr的普通指针必须指向动态分配的内存,这是因为智能指针默认用delete释放它关联的内存。 
如果出于一些目的,必须用不指向动态分配内存的指针来初始化智能指针,就必须自定义一种释放内存的方式(可以理解为重载delete)。 
详细例子在3.shared_ptr支持的操作中介绍。


3.shared_ptr支持的操作

操作 解释
shared_ptr< T > sp 创建空智能指针,可以指向T类型的对象
sp.get() 返回sp中保存的指针。PS:一定要小心使用,如果智能指针释放了对象,返回的指针指向的对象也将消失
sp.reset() 若sp是唯一指向该对象的shared_ptr,reset释放该对象
sp.reset(p) sp不在指向原来它指向的对象,指向内置指针p指向的对象,这里p是被new动态分配内存的
sp.reset(p,d) sp不在指向原来它指向的对象,指向内置指针p指向的对象,这里p将会被可调用对象d释放
sp.use_count() 返回sp指向对象的引用计数
sp.unique() 如果sp.use_count() == 1,返回true否则返回false
swap(sp1,sp2) 交换两个智能指针
shared_ptr< T > sp(p) sp管理内置指针p,p必须是被new动态分配内存的,而且能转换为T*类型
shared_ptr< T > sp(p,d) sp接管内置指针p指向对象的所有权,p必须能准换为T*类型,sp将使用可调用对象d代替delete
shared_ptr< T > sp1(sp2,d) sp1是shared_ptr sp2的拷贝,使用可调用对象d代替delete

上述操作表中,引入了一种新的做法,使用可调用对象d来释放内置指针

这基于这样的假设: 
内置指针p指向的对象不是由new动态分配的,然而我们还是用了p初始化了sp(shared_ptr),此时就必须用“重载”delete函数,顺利释放p的内存。 
这样是很麻烦的,具体的例子可以看这里: 
C++ Primer 第五版 练习12.4

C++ Primer建议不要混合使用普通指针和智能指针,更加推荐使用智能指针。 
为此,介绍几个例子:

假如说存在函数:

void process(shared_ptr<int> ptr)
{
    //do something 
}//ptr离开作用域,被ptr被销毁,但是它指向的内存不会被释放

此函数的正确使用方法传递shared_ptr:

shared_ptr<int> ptr(new int(42));
//ptr指向对象引用计数为1
process(ptr);
//process中ptr指向的对象引用计数为2
int i = *ptr;
//正确,引用计数为2

典型的错误用法如下:

int * x(new int(1024));
process(x);
//错误,内置指针不能隐式的转换为shared_ptr
process(shared_ptr<int> (x));
//正确,process中ptr.use_count() == 1,process结束时ptr指向的内存会被释放!
int i = *x;
//未定义行为,x是空悬指针!

首先,语句shared_ptr<int>(x)相当于一个强制转换,得到一个临时变量shared_ptr,调用process时,相当于void process(shared_ptr<int> ptr(x)){},那么当process结束的时候,ptr指向的对象的引用计数变为0,内存被释放,也就是x指针指向的内存被释放,x变为了一个空悬指针。

记住:当shared_ptr绑定到内置指针时。我们就把内存的管理责任教给了shared_ptr,不要再用内置指针访问该内存。

出于近乎相同的理由,不要使用get返回的内置指针初始化其他的智能指针!

猜你喜欢

转载自blog.csdn.net/Lasuerte/article/details/82014221