1 .智能指针的发展史
智能指针的发展要从RAII说起,RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。
(1)C++98提出auto _Ptr,auto_Ptr的设计是让两个指针指向同一块空间,但是这样会存在释放不当,出现程序崩溃的问题。
(2)在Boost库中设计了Scoped_Ptr,Shared_Ptr,Weak_Ptr这三种只能指针
(3)C++11经过进一步改进设计出了Unique_ptr,shared_ptr,Weak_ptr 这三种智能指针,Unique_ptr与相类似。
2.auto_Ptr/Scoped_Ptr/Shared_ptr/Weak_Ptr的设计思想、缺陷,以及它们各自的模拟实现?
(1) auto_Ptr的简单实现
auto_Ptr的几点注意事项:
auto_Ptr不能共享所有权
auto_Ptr不能指向数组
auto_Ptr不能作为容器的成员
不能通过赋值操作来初始化auto_Ptr
template<class T>
class auto_Ptr
{
public:
auto_Ptr(T* ptr)
:_ptr(ptr)
{}
// ap2(ap1)
auto_Ptr(auto_Ptr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = NULL;
}
// ap1 = ap2
auto_Ptr<T>& operator=(auto_Ptr<T>& ap)
{
if (this != &ap)
{
if (_ptr)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~auto_Ptr()
{
if (_ptr)
{
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
};
可以看到一个很明显的缺陷 当使用了构造函数和拷贝构造函数时,原来的智能指针就失效了,使用权交给了新的智能指针,这种方式叫做管理权转移,任何时候只能有一个对象指向那块区域,当有看、两个指针指向同一块区域时,程序会崩溃
(2)Boost阶段 Boost阶段提出Scoped_Ptr 守卫指针 防拷贝
SharedPtr的提出主要是建立在AutoPtr的基础上,为了防止赋值和拷贝构造,可以用两种方法解决, 其一:只声明不实现
其二:声明成私有,这样,编译时就不会报错。
Scoped_Ptr的模拟实现
template<class T>
class Scoped_Ptr
{
public:
Scoped_Ptr(T* ptr)
:_ptr(ptr)
{}
~Scoped_Ptr()
{
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
Scoped_Ptr(const Scoped_Ptr<T>&);
Scoped_Ptr<T>& operator=(const Scoped_Ptr<T>&);
protected:
T* _ptr;
};
Shared_Ptr的 简单实现
相对 于AutoPtr,Shared _Ptr 解决了指针共享的所有权问题,Shared_Ptr引入了引用计数,可以被自由的拷贝和赋值。
template<class T, class Del>
class Shared_Ptr
{
public:
Shared_Ptr()
:_ptr(NULL)
,_refCount(NULL)
{}
Shared_Ptr(T* ptr = NULL, Del del)
:_ptr(ptr)
,_refCount(new int(1))
,_del(del)
{}
~Shared_Ptr()
{
if (--(*_refCount) == 0)
{
cout<<"delete"<<endl;
delete _ptr;
_del(_ptr);
delete _refCount;
}
}
Shared_Ptr(const Shared_Ptr<T, Del>& sp)
:_ptr(sp._ptr)
,_refCount(sp._refCount)
{
++(*_refCount);
}
sp2 = sp3
Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp)
{
if (this != &s)
if(_ptr != sp._ptr)
{
if (--(*_refCount) == 0)
{
delete _ptr;
delete _refCount;
}
_ptr = sp._ptr;
_refCount = sp._refCount;
++(*_refCount);
}
return *this;
}
int RefCount()
{
return *_refCount;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* GetPtr() const
{
return _ptr;
}
protected:
T* _ptr;
int* _refCount;
Del _del;
};
Shared_ptr会带来循环引用问题。Weakptr 是用来解决Shared_ptr循环引用的缺陷问题,WeakPtr是一个弱指针,weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少.
WeakPtr的模拟实现
template<class T>
class Weak_Ptr
{
public:
Weak_Ptr()
:_ptr(NULL)
{}
Weak_Ptr(const Shared_Ptr<T>& sp)
:_ptr(sp.GetPtr())
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
};
虽然通过弱引用指针可以有效的解除循环引用, 但这种方式必须在程序员能预见会出现循环引用的情况下才能使用, 也可以是说这个仅仅是一种编译期的解决方案, 如果程序在运行过程中出
了循环引用, 还是会造成内存泄漏。
循环引用问题
你中有我,我中有你最后都导致双放都无法释放,最终导致内存泄漏,以双链表为例
struct ListNode
{
Weak_Ptr<ListNode> _prev;
Weak_Ptr<ListNode> _next;
~ListNode()
{
cout<<"~ListNode()"<<endl;
}
};
void TestCycleRef()
{
Shared_Ptr<ListNode> cur = new ListNode;
Shared_Ptr<ListNode> next = new ListNode;
cur->_next = next;
next->_prev = cur;
}
在这里,_next和_prev都是成员变量,第一块空间要想释放必须在第二块空间被释放之后才能释放,但由于第一块空间有一个节点指向第二块空间,所以只有第一块空间释放之后第二块空间才能释放,这样就带来了循环引用问题。