C++11智能指针之shared_ptr
shared_ptr
第一种智能指针是shared_ptr
,它有一个叫做共享所有权(sharedownership)的概念。shared_ptr
的目标非常简单:多个指针可以同时指向一个对象,当最后一个shared_ptr
离开作用域时,内存才会自动释放。
创建:
void main( )
{
shared_ptr<int> sptr1( new int );
}
使用make_shared
宏来加速创建的过程。因为shared_ptr
主动分配内存并且保存引用计数(reference count
),make_shared
以一种更有效率的方法来实现创建工作。
void main( )
{
shared_ptr<int> sptr1 = make_shared<int>(100);
}
上面的代码创建了一个shared_ptr
,指向一块内存,该内存包含一个整数100
,以及引用计数1
.如果通过sptr1
再创建一个shared_ptr
,引用计数就会变成2. 该计数被称为强引用(strong reference)
,除此之外,shared_ptr
还有另外一种引用计数叫做弱引用(weak reference)
,后面将介绍。
shared_ptr<int> sptr2 = sptr1; //引用计数增加为2
通过调用use_count()
可以得到引用计数, 据此你能找到shared_ptr
的数量。当debug的时候,可以通过观察shared_ptr
中strong_ref
的值得到引用计数。
析构
shared_ptr
默认调用delete
释放关联的资源。如果用户采用一个不一样的析构策略时,他可以*指定构造这个shared_ptr
的策略。下面的例子是一个由于采用默认析构策略导致的问题:
class Test
{
public:
Test(int a = 0 ) : m_a(a)
{
}
~Test( )
{
cout<<"Calling destructor"<<endl;
}
public:
int m_a;
};
void main( )
{
shared_ptr<Test> sptr1( new Test[5] );
}
在此场景下,shared_ptr
指向一组对象,但是当离开作用域时,默认的析构函数调用delete
释放资源。实际上,我们应该调用delete[]
来销毁这个数组。用户可以通过调用一个函数,例如一个lamda
表达式,来指定一个通用的释放步骤。
void main( )
{
shared_ptr<Test> sptr1( new Test[5],
[ ](Test* p) { delete[ ] p; } );
/*
//lamda表达式等价于:调用函数
void del[](Test* p){
delete[ ] p;
}
*/
}
接口
就像一个普通指针一样,shared_ptr
也提供解引用操作符*
,->
。除此之外,它还提供了一些更重要的接口:
get()
: 获取shared_ptr
绑定的资源.
reset()
: 释放关联内存块的所有权,如果是最后一个指向该资源的shared_ptr
,就释放这块内存。
unique
: 判断是否是唯一指向当前内存的shared_ptr
.
operator bool
: 判断当前的shared_ptr
是否指向一个内存块,可以用if 表达式判断。
OK,上面是所有关于shared_ptr
的描述,但是shared_ptr
也有一些问题:
问题1:
void main( )
{
shared_ptr<int> sptr1( new int );
shared_ptr<int> sptr2 = sptr1;
shared_ptr<int> sptr3;
sptr3 =sptr1
下表是上面代码中引用计数变化情况:
所有的shared_ptrs
拥有相同的引用计数,属于相同的组。上述代码工作良好,让我们看另外一组例子。
void main( )
{
int* p = new int;
shared_ptr<int> sptr1( p);
shared_ptr<int> sptr2( p );
}
上述代码会产生一个错误,因为两个来自不同组的shared_ptr
指向同一个资源。下表给你关于错误原因的图景:
避免这个问题,尽量不要从一个裸指针(naked pointer)
创建shared_ptr
.
问题2
class B;
class A
{
public:
A( ) : m_sptrB(nullptr) { };
~A( )
{
cout<<" A is destroyed"<<endl;
}
shared_ptr<B> m_sptrB;
};
class B
{
public:
B( ) : m_sptrA(nullptr) { };
~B( )
{
cout<<" B is destroyed"<<endl;
}
shared_ptr<A> m_sptrA;
};
//***********************************************************
void main( )
{
shared_ptr<B> sptrB( new B );
shared_ptr<A> sptrA( new A );
sptrB->m_sptrA = sptrA;
sptrA->m_sptrB = sptrB;
}
分析:什么都没有输出!
上面的代码产生了一个循环引用.A
对B
有一个shared_ptr
, B
对A
也有一个shared_ptr
,与sptrA
和sptrB
关联的资源都没有被释放,参考下表:
当sptrA
和sptrB
离开作用域时,它们的引用计数都只减少到1,所以它们指向的资源并没有释放!!!!!(此处不明白!)
总结:
1.如果几个shared_ptrs
指向的内存块属于不同组,将产生错误。(问题1)
2.如果从一个普通指针创建一个shared_ptr
还会引发另外一个问题。在上面的代码中,考虑到只有一个shared_ptr
是由p
创建的,代码可以好好工作。万一程序员在智能指针作用域结束之前删除了普通指针p
。天啦噜!!!又是一个crash。
int main()
{
int* p = new int;
shared_ptr<int> sptr1(p);
shared_ptr<int> sptr2(p);
delete p; //oops!
*p = 50;
cout << *p << endl;
getchar();
return 0;
}
3.循环引用:如果共享智能指针卷入了循环引用,资源都不会正常释放。(问题2)
为了解决循环引用,C++
提供了另外一种智能指针:weak_ptr