本文转自:
http://blog.csdn.net/wxt_hillwill/article/details/69950608
★智能指针
智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。智能指针就是模拟指针动作的类。所有的智能指针都会重载 -> 和 * 操作符。智能指针还有许多其他功能,比较有用的是自动销毁。这主要是利用栈对象的有限作用域以及临时对象(有限作用域实现)析构函数释放内存。当然,智能指针还不止这些,还包括复制时可以修改源对象等。智能指针根据需求不同,设计也不同:
auto_ptr 管理权转让(不建议使用)
scoped_ptr 防拷贝
shared_ptr 引用计数
★模拟实现auto_ptr
☆实现思路:AutoPtr的成员变量主要有T* _ptr, bool _owner,主要实现原理是在构造对象时赋予其空间的所有权,在析构函数中通过_owner的真假来释放所有权,并且在拷贝或赋值后将_owner设为false,转移空间的所有权。但此做法的问题是:如果要释放一个拷贝出来的对象,虽然原对象的_owner为false,但会多次释放同一块空间,导致内存泄漏。
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)
:_ptr(ptr)
, _owner(true)
{}
AutoPtr(const AutoPtr<T>& ap)
:_ptr(ap._ptr)
{
ap._owner = false;
_owner = true;
}
AutoPtr<T>& operator=(const AutoPtr<T>& ap)
{
if (&s != this)
{
delete _ptr;
_ptr = ap._ptr;
ap._owner = false;
_owner = true;
}
return *this;
}
~AutoPtr()
{
if (_ptr)
{
delete _ptr;
_ptr = NULL;
_owner = false;
}
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr;
bool _owner;
};
问题是:用ap1拷贝构造ap2,ap1和ap2指向同一块空间,出了作用域之后,ap2会释放空间还给系统,但ap1仍然指向这块空间,虽然它的_owner已经改为false。
☆改进后还是管理空间的所有权转移,但这种方法里没有_owner。构造和析构函数的实现方法类似,但拷贝和赋值后直接将_ptr赋为空,禁止其再访问原来的空间。
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{}
AutoPtr(AutoPtr<T> &ap) //拷贝后将原有指向同一块内存的指针置空
:_ptr(ap._ptr)
{
ap._ptr = NULL;
}
AutoPtr<T>& operator=(AutoPtr<T> &ap)
{
if (this != &ap)
{
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* GetPtr()//返回原指针_ptr
{
return _ptr;
}
T* operator->()
{
return _ptr;
}
~AutoPtr()
{
cout << "~AutoPtr()" << endl;
if (_ptr)
{
delete _ptr;
_ptr = NULL;
}
}
private:
T* _ptr;
};
★模拟实现ScopedPtr
☆因为智能指针容易出现拷贝时释放两次的情况,所以ScopedPtr主要是为了防止拷贝,防止拷贝的两条必要条件是:①设置保护限定符(将拷贝构造函数和赋值运算符的重载设置成保护或者私有的,这样就可以保证其他人在不知情的情况下定义拷贝构造和赋值运算符重载了);②对拷贝构造函数和赋值运算符重载只声明不定义(如果不这么做,编译器就会自动合成拷贝构造和赋值运算符重载,就实现不了防拷贝的目的了)。
template<class T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
T& operator*()
{
return *_ptr;
}
T* GetPtr()//返回原指针_ptr
{
return _ptr;
}
T* operator->()
{
return _ptr;
}
~ScopedPtr()
{
cout << "~ScopedPtr()" << endl;
if (_ptr)
{
delete _ptr;
_ptr = NULL;
}
}
protected://防拷贝:此处为privated或protecte;拷贝构造和赋值函数只声明不定义
ScopedPtr(const ScopedPtr<T> &sp);
ScopedPtr<T>& operator==(const ScopedPtr<T> &sp);
private:
T* _ptr;
};
★ 模拟实现SharedPtr
☆SharedPtr顾名思义就是共享指针,思想是引用计数。引入指针变量pCount,指向同一块空间对其计数,当只有一个指针指向空间时再释放空间,这样可以解决多个指针指向同一块空间释放多次导致内存泄漏的问题。
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pCount(new long(1))
{}
SharedPtr()
:_ptr(NULL)
, _pCount(new long(1))
{}
SharedPtr<T>(const SharedPtr<T>& sp)
: _ptr(sp._ptr)
, _pCount(sp._pCount)
{
++(*_pCount);
}
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
if (&sp != this)
{
if (--(*_pCount) == 0)
{
delete _ptr;
delete _pCount;
}
_ptr = sp._ptr;
_pCount = sp._pCount;
++(*_pCount);
}
return *this;
}
~SharedPtr()
{
if (_ptr)
{
if (--(*_pCount) == 0)
{
delete _ptr;
delete _pCount;
}
}
}
T& operator*()
{
return *_ptr;
}
long GetCount()
{
return *(_pCount);
}
T* GetPtr()
{
return _ptr;
}
private:
T* _ptr;
long* _pCount;
};
简化版的引用计数智能指针SharedPtr还存在问题:①引用计数更新存在线程安全问题;②循环引用问题:③定制 删除器。
☆线程安全问题:“Boost”文档对于shared_ptr的线程安全有一段专门的描述,总结为以下几点:①同一个 shared_ptr被多个线程“读”是安全的;②同一个shared_ptr被多个线程“写”是不安全的;③共享引用计数的不同的shared_ptr被多个线程“写”是安全的。
☆循环引用问题:下面的例子中,p1和p2的引用计数都是1,进行析构p1时首先考虑释放p2的空间,而释放p2又 依赖于释放p1的空间。这样就形成了无限循环,最终造成内存泄漏。
using namespace boost;
template <typename T>
struct ListNode
{
~ListNode()
{
cout<<"ListNode()"<<endl;
}
shared_ptr<ListNode<T>> _prev;
shared_ptr<ListNode<T>> _next;
};
void Test()
{
shared_ptr<ListNode> p1(new ListNode());
shared_ptr<ListNode> p2(new ListNode());
cout<<"p1->Count:"<<p1.use_count()<<endl;
cout<<"p2->Count:"<<p2.use_count()<<endl;
p1->_next = p2;
//p1结点的_next指向p2
p2->_prev = p1;
//p2结点的_prev指向p1
cout<<"p1->Count:"<<p1.use_count()<<endl;
cout<<"p2->Count:"<<p2.use_count()<<endl;
}
int main()
{
Test();
system("pause");
return 0;
}
这个问题的解决方法是:引入弱引用智能指针weak_ptr来打破循环引用。当至少有一个强引用share_ptr时,被 引用的对象就不能被释放。而弱引用weak_ptr仅仅是当被引用对象存在时的一个引用。弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针。并且弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。使用方法如下:
using namespace boost;
template <typename T>
struct ListNode
{
~ListNode()
{
cout<<"ListNode()"<<endl;
}
weak_ptr<ListNode<T>> _prev;
weak_ptr<ListNode<T>> _next;
};
☆定制删除器:如果指针是一个指向文件类型的,在析构函数中只需关闭文件即可,而不是释放空间;如果空间是 通过malloc开辟出来的,那么在析构函数中要调用free函数,而不是delete操作符。上述问题通过仿函数就可以解决。具体代码如下:
template<class T>
class Del
{
public:
void operator() (const T *ptr)
{
delete ptr;
}
};
//仿函数实现FClose和Free
struct FClose
{
void operator() (void* ptr)
{
cout << "Close" << endl;
fclose((FILE*)ptr);
}
};
struct Free
{
void operator() (void* ptr)
{
cout << "Free" << endl;
free(ptr);
}
};
template<class T,class Deleter=Del<T>>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pCount(new long(1))
{}
SharedPtr(T* ptr,Deleter del)
:_ptr(ptr)
, _pCount(new long(1))
, _del(del)
{}
SharedPtr<T>& operator=(SharedPtr<T> sp)
{
swap(_ptr, sp._ptr);
swap(_pCount, sp._pCount);
return *this;
}
~SharedPtr()
{
cout << "~SharedPtr()" << endl;
Release();
}
T& operator*()
{
return *_ptr;
}
T* GetPtr()//返回原指针_ptr
{
return _ptr;
}
T* operator->()
{
return _ptr;
}
void Release()//释放内存
{
if (--(*_pCount) == 0)
{
_del(_ptr);
delete _pCount;
}
}
private:
T* _ptr;
long* _pCount;
Deleter _del;
};
void Test()
{
SharedPtr<int> sp(new int(6));
SharedPtr<FILE, FClose> sp1(fopen("test.text", "w"), FClose());
SharedPtr<int, Free> sp2((int*)malloc(sizeof(int)* 6), Free());
}