智能指针

智能指针    ——  指针 + RAII

RAII :资源分配即初始化。定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化。在析构函数完成资源的清理,可以保证资源的正确初始化和释放。

智能指针的发展历史及设计思想和缺陷
早期C++98                         auto_ptr                                ——管理权转移. 这种方式有缺陷

boost(非官方)                 scoped_ptr/scoped_array       ——守卫指针,防拷贝,简单粗暴
                                           shared_ptr/shared_array       ——共享指针,引用计数,复杂. 缺陷:循环引用
                                           weak_ptr      ——弱指针,不能单独存在,辅助解决shared_ptr的循环引用问题

C++11                                unique_ptr
                                            shared_ptr
                                            weak_ptr
在我们实际写代码中,不免遇到下面的这种情况,这是非常危险的,会 导致程序出现内存泄漏
int* p1 = new int(2);

.....

if (p1)
{
      return;    //执行流改变
}
......

delete p1;
所以我们就引入了智能指针。 一个简单的例子来说明一下智能指针要做的事情(资源的分配和释放):

auto_ptr的管理权转移(模拟实现)
template<class T>
class AutoPtr
{
public:
          AutoPtr(T* ptr)
                   :_ptr(ptr)
          {}
          ~AutoPtr()
          {
                   if (_ptr)
                   {
                             printf("0x%p\n", _ptr);
                             delete _ptr;
                   }
          }

          T& operator*()
          {
                   return *_ptr;
          }

          T* operator->()
          {
                   return _ptr;
          }

private:
          T* _ptr;
};
但这还不够,在实际中,我们还要写它的拷贝构造和赋值运算符的重载
因为出现这样的代码时,会出错
void TestAutoPtr()
{
          AutoPtr<int> ap1(new int(2));
          AutoPtr<int> ap2(ap1);  //浅拷贝 ,两个对象指向同一块空间,会对同一块空间释放两次
}

所以我们要自己写拷贝构造和赋值运算符重载,进行 管理权转移
       //ap2(ap1)
          AutoPtr(AutoPtr<T>& ap)
                   :_ptr(ap._ptr)
          {
                   ap._ptr = NULL;
          }

          //ap1 = ap2
          AutoPtr<T>& operator=(AutoPtr<T>& ap)
          {
                   if (this != &ap)
                   {
                             if (_ptr)
                                      delete _ptr;
                             _ptr = ap._ptr;
                             ap._ptr = NULL;
                   }
                   return *this;
          }
//测试
void TestAutoPtr()
{
          AutoPtr<int> ap1(new int(2));
          AutoPtr<int> ap2(ap1);
          
          AutoPtr<int> ap3(new int(2));
          ap2 = ap3;

          *ap3 = 30;  //因为ap3已被置NULL,再对其解引用就会出错
}
所以,对于AutoPtr我们还是不要使用它。

scope_ptr的防拷贝(模拟实现)
这里的防拷贝指的是不让拷贝,这样就不会出现同一块空间释放两次的情况
template<class T>
class ScopedPtr
{
public:
          ScopedPtr(T* ptr)
                   :_ptr(ptr)
          {}
          ~ScopedPtr()
          {
                   if (_ptr)
                   {
                             printf("0x%p\n", _ptr);
                             delete _ptr;
                   }
          }

          T& operator*()
          {
                   return *_ptr;
          }

          T* operator->()
          {
                   return _ptr;
          }

          //防拷贝,拷贝构造和赋值运算符重载定义为私有,且只声明不实现
private:
          ScopedPtr(const ScopedPtr<T>& sp);
          ScopedPtr<T>& operator=(const ScopedPtr<T>& sp);

private:
          T* _ptr;
};

void TestScopedPtr()
{
          ScopedPtr<int> sp1(new int(1));
          //ScopedPtr<int> sp2(sp1);

          ScopedPtr<int> sp3(new int(2));
          //sp3 = sp1;
}
shared_ptr的引用计数(模拟实现)
template<class T>
class SharedPtr
{
public:
          friend class weak_ptr<T>; //随后会用到

          SharedPtr(T* ptr)
                   :_ptr(ptr)
                   , _refCount(new int(1))
          {}
          ~SharedPtr()
          {
                   if (--(*_refCount) == 0)
                   {
                             printf("0x%p\n", _ptr);
                             delete _ptr;
                             delete _refCount;
                   }
          }
          
          T& operator*()
          {
                   return *_ptr;
          }

          T* operator->()
          {
                   return _ptr;
          }

          //sp2(sp1)
          SharedPtr(const ScopedPtr<T>& sp)
                   :_ptr(sp._ptr)
                   , _refCount(sp._refCount)
          {
                   (*_refCount)++;
          }

          //sp2 = sp1
          SharedPtr<T>& operator=(const SharedPtr<T>& sp)
          {
                   if (this != &sp._ptr)
                   {
                             if (--(*_refCount) == 0)
                             {
                                      delete _ptr;
                                      delete _refCount;
                             }
                             _ptr = sp._ptr;
                             _refCount = sp._refCount;
                             (*_refCount)++;
                   }
                   return *this;
          }

private:
          T* _ptr;
          int* _refCount;
};
在运行如下的代码时就会出现 循环引用 的问题
struct ListNode
{
	int _data;

	SharedPtr<ListNode> _next;
	SharedPtr<ListNode> _prev;

	ListNode(int x)
		:_data(x)
		, _next(NULL)
		, _prev(NULL)
	{
		cout << "ListNode()" << endl;
	}

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

void Test()
{
	SharedPtr<ListNode> cur(new ListNode(1));
	SharedPtr<ListNode> next(new ListNode(1));

	cur->_next = next;
	next->_prev = cur;
}

我们可以看出并没有对cur和next所管理的空间进行释放。
分析一下:

出作用域时cur和next的生命周期到了,就会调析构函数。cur->_next = next;  next->_prev = cur;cur等next释放,next等cur释放,这样就造成了 循环引用,导致内存泄漏 的问题。
shared_ptr单独使用的时候会出现循环引用的问题,造成内存泄漏,所以标准库又从boost库当中引入了weak_ptr(弱指针)。 注意:shared_ptr不能单独使用
由于弱指针并不修改该对象的引用计数,这意味这弱指针它并不对对象的内存进行管理,在功能上类似普通的指针。然而一个比较大的区别是:弱指针能检测到所管理的对象是否已经被释放,从而避免访问非法内存。 
下面我们来 模拟实现weak_ptr
template<class T>
class WeakPtr
{
public:
          WeakPtr()
                   :_ptr(NULL)
          {}

          WeakPtr(const SharedPtr<T>& sp)
                   :_ptr(sp._ptr)
          {}

          T& operator*()
          {
                   return *_ptr;
          }

          T* operator->()
          {
                   return _ptr;
          }

          WeakPtr<T>& operator= (const SharedPtr<T>& sp)
          {
                   _ptr = sp._ptr;
                   return *this;
          }
private:
          T* _ptr;
};
可以看到,WeakPtr必须从一个SharedPtr或者另一个WeakPtr转换而来,不能使用new对象进行构造。这也说明,进行该对象的内存管理的是那个强引用的SharedPtr。WeakPtr只是提供了对管理对象一个访问手段。

然后再测试:
struct ListNode
{
          int _data;

          WeakPtr<ListNode> _next;
          WeakPtr<ListNode> _prev;

          ListNode(int x)
                   :_data(x)
                   , _next(NULL)
                   , _prev(NULL)
          {
                   cout << "ListNode()" << endl;
          }

          ~ListNode()
          {
                   cout << "~ListNode()" << endl;
          }
};

void Test()
{
          WeakPtr<ListNode> cur(new ListNode(1));
          WeakPtr<ListNode> next(new ListNode(1));

          cur->_next = next;
          next->_prev = cur;
}

这样就对两个结点的空间成功的释放了。
因为弱指针不更改引用计数, 类似普通指针,只要把循环引用的一方使用弱指针,即可解除循环引用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值