一、智能指针
C++98最早提供的智能指针:
auto_ptr
Boost 提供的智能指针:
scope_ptr/scope_array
unique_ptr
shared_ptr
weak_ptr
下面,我分别针对上述提及指针一一说明。
1、auto_ptr: 自动管理由new动态分配的单个对象,其管理对象的生命周期结束时会调用delete自动释放。
- 任意转移资源所有权,用户未知
//相关操作:
auto_ptr<int> ptr1(new int(5));
A ptr2(new int(10));//初始化对象ptr2
ptr1.get(); //返回底层对象地址
ptr1.reset(ptrr2); //销毁指定的对象,并存储新的指针(指针重置)
ptr1.release(); //实现auto_ptr底层对象之间的初始化、赋值(释放当前指针的所有权)
2、scope_ptr: 在auto_ptr的基础上增加了一些限制范围。
- 不能转换:其管理的对象的生命周期仅限于一个区间
- 不能共享:拷贝、赋值均为private
- 管理数组对象:用scope_array
3、unique_ptr: 这个只能指针可以完全代替auto_ptr, 甚至还对齐进行了扩展。
- 任意转移资源所有权,用户可感知
- 可以管理数组对象,即调用了delete[]
unique_ptr<int> ptr1 (new int(5));
unique_ptr<int []> ptr2(new int[3]{1, 2, 3});
4、shared_ptr: “最像指针的智能指针”。
- 有管理对象的所有使用权
- 能实现共享
- 引用计数:通过引用计数来记录不同指针对同一对象的管理个数,同时决定该对象的释放时间,可以避免不引用计数的智能指针出现的问题,下面会详细说明。
(引用计数为0时,说明此时已经没有指针对其进行管理了)
5、weak_ptr: “观察指针”、“悬浮的指针”,其出现完全针对于shared_ptr的辅助。
- 指向由shared_ptr管理的对象,但不控制管理对象的生命周期,即不会改变引用计数值
- operator-> 不允许通过weak_ptr访问对象
- 观察指针:weak_ptr指针可以通过lock()操作提升为shared_ptr指针
- 悬浮的指针:weak_ptr指针可以通过expired()操作检测出失效指针(指向对象引用计数为0,已销毁)
shared_ptr<int> sp(new int(5)); //此时指向对象引用计数为1
weak_ptr <int> wp(sp); //wp也指向同一对象,但引用计数不加1
wp.expired(); //检测sp指向对象是否销毁
shared_ptr<int> p = wp.lock(); //将弱智能指针提升为强智能指针,此时指向对象引用计数加1
assert(p != NULL) //lock()成功, p可以进行operator ->操作调用sp资源
//lock()失败,资源已经释放
总结:
- 智能指针均指向其对象的地址,故其使用方法同裸指针一样;
- 底层操作实现均内联,因此并不比直接使用指针的代价更高;
- 智能指针都是将new/delete或new[]/delete[]包装到堆上的动态对象中;
- 将栈中内容出栈自动释放的特性移植到了堆上,实现了资源的自动释放;
- 智能指针可以避免裸指针可能会出现的内存泄漏问题。
二、引用计数
显而易见,二者之间的差别就在于是否对资源进行引用计数,
上面提到的智能指针仅shared_ptr 与 weak_ptr 是引用计数的。
引用计数的原因:
- 不带引用计数的指针—>浅拷贝问题。
带引用计数的指针,它们在处理多个指针均指向同一内存资源这一情况时,会先原来的auto_ptr置空,只有最新的auto_ptr才能访问资源,而这些操作都是透明化的,这样若再次使用旧的auto_ptr便会产生浅拷贝问题 - 带引用计数的指针,在遇到上述同一情况时,仅对该块内存的使用次数加一,多个指针均能对其进行访问操作,此时该内存资源为共享的,当且仅当引用计数为0,释放该内存资源,此时也不会出现野指针
三、实现MYshared_ptr
class MYshared_ptr
{
public:
//增加一个引用计数
void addRef(void *ptr)
{
list<Node> iterator it = nodeList.begin();
for(; it != nodeList.end(); ++it)
{
if(ptr == it->_resptr)
{
it->_count++;
return;
}
}
nodeList.push_back(Node(ptr));
}
//减少一个引用计数
void delRef(void *ptr)
{
list<Node> iterator it = nodeList.begin();
for(; it != nodeList.end(); ++it)
{
if (it->_resptr == ptr)
{
if (it->_count == 1)
{
nodeList.erase(it);
return 0;
}
else
{
it->_count--;
return it->_count;
}
}
}
return -1;
}
private:
struct Node
{
Node(void *ptr = NULL):_resptr(ptr), _count(1){}
void *_resptr;
int _count;
}; //某一资源信息
list<Node> nodeList; //保存智能指针指向的所有资源
};
四、智能指针的循环/交叉引用问题
该问题出现在强智能指针的引用计数当中,结果会导致资源无法释放。
class B;
class A
{
public:
A(){ cout << "A()" << endl; }
~A(){ cout << "~A()" << endl; }
shared_ptr<B> _ptrb;
};
class B
{
public:
B(){ cout << "B()" << endl; }
~B(){ cout << "~B()" << endl; }
shared_ptr<A> _ptra;
};
int main(int argc, char* argv[])
{
shared_ptr<A> ptra(new A());
shared_ptr<B> ptrb(new B());
ptra->_ptrb = ptrb;
ptrb->_ptra = ptra;
}
//通过调试运行,得到结果为
A()
B()
而A、B均为析构,调试发现程序结束前A、B的引用计数count均为1,这也就是造成没有析构的原因。
shared_ptr和weak_ptr的使用建议:
创建对象的地方使用强智能指针,其它地方一律使用弱智能指针