C++动态内存管理是通过一对运算符完成, new/delete. new:在动态内存中对象分配一块空间并返回一个指向该对象的指针。delete: 通过指针销毁其指向的对象,并释放与之相关的内存空间。
常见的两种问题是: 1. 忘记释放内存,内存泄漏
2. 在还有其他指针指向对象的时候释放对象,造成指针引用非法内存---- 挂起引用
智能指针被引入解决上述问题,在构造时分配内存,当离开作用域时负责自动释放指向对象的内存。
C++98 提供第一种智能指针 auto_ptr
auto_ptr
void main( )
{
try
{
std::auto_ptr<Test> p( new Test(5) );
Fun( );
cout<<p->m_a<<endl;
}
catch(...)
{
cout<<"Something has gone wrong"<<endl;
}
}
上述代码中,动态申请了一块内存存放Test对象,然后绑定到auto_ptr p上, 尽管Fun函数可能会抛出异常,但是p离开作用域时一定会释放内存。
问题一:
指针传递时,比如把auto_ptr p1赋值给另外一个auto_ptr p2, 此时发生所有权转移,p1把关联的内存块所有权传递给p2, 当p2离开作用域时,就会释放指向的内存空间, 但此时p1仍然指向这块内存。
问题二:
不能指向一组对象,因为一组对象在释放时应该使用的时delete [] 而不是delete
问题三:
与标准容器不能一起使用。
为此,C++ 11提供了一组新的智能指针:
- shared_ptr
- unique_ptr
- weak_ptr
shared_ptr
共享所有权,多个指针指向一个对象,只有当所有的shared_ptr都离开作用域时,内存才被释放。
创建:
void main( )
{
shared_ptr<int> sptr1( new int );
shared_ptr<int> sptr1 = make_shared<int>(100);
此时, shared_ptr
指向一块内存,内存中包含一个整数以及引用计数 1, 如果通过sptr1再创建一个shared_ptr,引用计数就会变为2.
通过调用use_count()
得到引用计数
析构
shared_ptr
默认调用delete释放关联资源,不过用户可以指定析构函数,
void main( )
{
shared_ptr<Test> sptr1( new Test[5] );
}
此时应该调用delete[]销毁数组,用户调用一个函数,指定释放步骤。
void main( )
{
shared_ptr<Test> sptr1( new Test[5],
[ ](Test* p) { delete[ ] p; } );
}
其他接口:
除了基本的解引用操作符 *, ->之外,还提供了:
- get() 获取绑定资源
- reset() 释放关联内存块的所有权,当是最后一个时,相当于释放内存
- unique 判断是否是唯一指向当前内存的shared_ptr
- operator bool: 判断当前shared_ptr是否指向内存块,使用if
问题:
循环引用:
void main( )
{
shared_ptr<B> sptrB( new B );
shared_ptr<A> sptrA( new A );
sptrB->m_sptrA = sptrA;
sptrA->m_sptrB = sptrB;
}
程序结束时,两种指针的引用计数都为1, 都不会释放。
两个组的shared_ptr指向同一个资源
void main( )
{
int* p = new int;
shared_ptr<int> sptr1( p);
shared_ptr<int> sptr2( p );
}
sptr1先出作用域,此时引用计数-1, 发现此时引用计数为0,释放空间,那么等到sptr2出作用域时就会发生错误。
为了解决循环引用的问题,引入weak_ptr
weak_ptr
weak_ptr 有 共享语义与不包含语义
,因此weak_ptr可以共享shared_ptr的资源。
weak_ptr本身不包含资源,不支持解引用操作 *, ->, 通过从weak_ptr中创建shared_ptr来使用。
创建
使用shared_ptr作为参数构造 weak_ptr, 从shared_ptr创建weak_ptr时增加共享指针的弱引用计数,但是当shared_ptr离开作用域时,弱引用计数不作为释放资源依据。只有强引用计数变为0才会释放指针指向资源。
void main( )
{
shared_ptr<Test> sptr( new Test );
// 弱引用计数1-2
weak_ptr<Test> wptr( sptr );// 1
weak_ptr<Test> wptr1 = wptr;// 2
}
当shared_ptr离开作用域时,资源释放,此时指向该shared_ptr的weak_ptr过期。通过use-count()返回强引用计数以及expired()函数可以判断weak_ptr是否指向有效资源。
调用lock()函数将得到shared_ptr或者将weak_ptr转型为shared_ptr
shared_ptr<Test> sptr2 = wptr.lock( );
此时增加强引用计数。
解决循环引用问题:
将对象指向另外对象的指针声明为 weak_ptr, 那么在循环引用的时候不会增加强引用计数,只会增加弱引用计数。
class B;
class A
{
public:
A( ) : m_a(5) { };
~A( )
{
cout<<" A is destroyed"<<endl;
}
void PrintSpB( );
weak_ptr<B> m_sptrB;
int m_a;
};
class B
{
public:
B( ) : m_b(10) { };
~B( )
{
cout<<" B is destroyed"<<endl;
}
weak_ptr<A> m_sptrA;
int m_b;
};
void A::PrintSpB( )
{
if( !m_sptrB.expired() )
{
cout<< m_sptrB.lock( )->m_b<<endl;
}
}
void main( )
{
shared_ptr<B> sptrB( new B );
shared_ptr<A> sptrA( new A );
sptrB->m_sptrA = sptrA;
sptrA->m_sptrB = sptrB;
sptrA->PrintSpB( );
}
unique_ptr
unique_ptr也是对auto_ptr的替换,遵循独占语义,任何时间点,资源只能唯一地被一个unique_ptr占用。 当unique_ptr离开作用域时,所包含资源释放,或者资源被其他资源重写,之前拥有资源也会释放。
创建
创建方法与shared_ptr一样,不过支持创建数组类型。
unique_ptr<int> uptr(new int);
unique_ptr<int []> uptr(new int [5])
unique表示独占,不支持拷贝以及赋值操作。当把unique_ptr赋值给其他对象时,资源所有权发生转移。
可以通过release() 函数 或者 reset函数将所有权转移。
unique_ptr<string> p2(p1.release());// 所有权从p1-p2;
unique_ptr<string> p3(new string("T"));
// p2重新指向p3
p2.reset(p3.release());
release函数返回的指针通常用于初始化另外一个智能指针或给另外的智能指针赋值。
其他方法:
release()方法 释放所有权返回指针
reset()方法释放所有权以及资源
u = null || u.reset() 释放u指向的对象