智能指针学习

包含指针的类需要特别注意复制控制,原因是复制指针时只是复制了指针中的地址,而不会复制指针指向的对象

    将一个指针复制到另一个指针时,两个指针指向同一对象。当两个指针指向同一对象时,可能使用任一指针改变基础对象。类似地,很可能一个指针删除了一对象时,另一指针的用户还认为基础对象仍然存在。指针成员默认具有与指针对象同样的行为。

大多数C++类采用以下三种方法之一管理指针成员:

    1指针成员采取常规指针型行为:这样的类具有指针的所有缺陷但无需特殊的复制控制!

    2类可以实现所谓的“智能指针”行为:通过对共享指针的对象进行计数,指针所指向的对象是共享的,但类能够防止悬垂指针

    3类采取值型行为:每个对象在建立的时候,不管是初始化或者复制构造,指针所指向的对象是唯一的,有每个类对象独立管理

1 常规指针:

#ifndef _HASPTR_H_
#define _HASPTR_H_

/************************************************************************/
/* 
 *	带指针成员的简单类,其指针共享同一对象,因而会出现悬垂指针
 *
 *	date:2016/9/13
 *
 */
/************************************************************************/

class HasPtr {
public:
	HasPtr(int *p , int i):ptr(p) , val(i){};
	~HasPtr(void){ }

public:
	int *get_ptr() const { return ptr; }
	int get_val() const { return val;}

	void set_ptr( int *p ) { ptr =p;}
	void set_val( int i) { val = i;}

	int get_ptr_val() const { return *ptr;}
	void set_ptr_val( int i) { *ptr = i;}

private:
	int *ptr;
	int val;
};

#endif

因为 HasPtr 类没有定义复制构造函数 , 所以复制一个 HasPtr 对象将复制两个成员,因为采取的常规的合成构造函数
#include <iostream>
#include "hasPtr.h"
using namespace std;

int main ()
{
	int obj=0;

	HasPtr ptr1(&obj,42);
	//copy 后 &ptr1.ptr==&ptr2.ptr
	cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl;

	HasPtr ptr2(ptr1);
	cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl;


	//因为ptr2是ptr1  copy过来的,而set_ptr_val()函数仅仅修改的是*ptr的值
	//ptr 的地址仍然不变,ptr1和ptr2还是相同
	ptr1.set_ptr_val(34);
	cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl;
	cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl;

	//改变了指针本身的值,因此两个指针所指向的内容不同
	int m=44;
	ptr1.set_ptr(&m);
	cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl;
	cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl;

	悬垂指针 ,因为ip和ptr中的指针指向同一对象,删除该对象,ptr中
	//的指针不再指向有效对象了

	//int *ip=new int (43);
	//HasPtr ptr(ip,10);
	//delete ip; //
	//ptr.set_ptr_val(0);
	//cout<<ptr.get_ptr()<<'\t'<<ptr.get_ptr_val()<<'\t'<<ptr.get_val()<<endl;

	system("pause");
	return 0;
}

2 智能指针

      智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。它的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
      智能指针就是模拟指针动作的类。所有的智能指针都会重载 -> 和 * 操作符。智能指针还有许多其他功能,比较有用的是自动销毁。这主要是利用栈对象的有限作用域以及临时对象(有限作用域实现)析构函数释放内存。当然,智能指针还不止这些,还包括复制时可以修改源对象等。智能指针根据需求不同,设计也不同(写时复制,赋值即释放对象拥有权限、引用计数等,控制权转移等)。auto_ptr 即是一种常见的智能指针。

比如上面类hasPtr中的成员ptr就可以跟引用计数绑定到一起,然后写到同一个类中:

<span style="font-family:Verdana;color:#333333;">/************************************************************************/
/* 
 *	定义一个单独的具体类用以封装使用计数和相关指针    
 *
 */
/************************************************************************/
//class U_Ptr , private class for use by hasPtr only
class U_Ptr
{
private:
	//将HasPtr设置成为友元类,使其成员可以访问U_Ptr的成员 
	friend class HasPtr;
	
	U_Ptr(int *p):  ip(p) , use(1) {}
	~U_Ptr() { delete ip; }

private:
	int *ip;
	size_t use;
};</span>

将所有的成员都设置成为private:我们不希望普通用户使用U_Ptr类,所以他没有任何public成员!


HasPtr类需要一个析构函数来删除指针。但是,析构函数不能无条件的删除指针。”
      条件就是引用计数。 如果该对象被两个指针所指,那么删除其中一个指针,并不会调用该指针的析构函数,因为此时还有另外一个指针指向该对象。看来,智能指针主要是预防不当的析构行为,防止出现悬垂指针。

     如上图所示,HasPtr就是智能指针,U_Ptr为计数器;里面有个变量use和指针ip,use记录了*ip对象被多少个HasPtr对象所指。假设现在又两个HasPtr对象p1、p2指向了U_Ptr,那么现在我delete  p1,use变量将自减1,  U_Ptr不会析构,那么U_Ptr指向的对象也不会析构,那么p2仍然指向了原来的对象,而不会变成一个悬空指针。当delete p2的时候,use变量将自减1,为0。此时,U_Ptr对象进行析构,那么U_Ptr指向的对象也进行析构,保证不会出现内存泄露。 

   新的HasPtr保存一个指向U_Ptr对象的指针,U_Ptr对象指向实际的int基础对象:

<span style="font-family:Verdana;color:#333333;">class Hasptr
{
public:
	Hasptr(int *p,int i):ptr(new U_Ptr(p)),val(i)
	{
		cout << "HasPtr constructor called ! " << "use = " << ptr->use << endl; 
	}
	//赋值控制成员
	Hasptr(const Hasptr &org):ptr(org.ptr),val(org.val)
	{
		++ptr->use;
		cout << "HasPtr copy constructor called ! " << "use = " << ptr->use << endl;
	}
	Hasptr &operator=(const Hasptr &rhs){
		// 增加右操作数中的使用计数 
		++rhs.ptr->use;
		// 将左操作数对象的使用计数减1,若该对象的使用计数减至0,则删除该对象  
		if(-- ptr ->use==0)
			delete ptr;
		ptr=rhs.ptr ;
		val=rhs.val ;
		return *this;
	}
	~Hasptr()
	{
		cout << "HasPtr distructor called ! " << "use = " << ptr->use << endl;  
		if(--ptr->use==0)
			delete ptr;
	}
	//return value
	int *get_ptr() const
	{
		return ptr->ip;
	}
	int get_val() const
	{
		return val;
	}

	//non const members changes the indicated members
	void set_ptr(int *p)
	{
		ptr->ip=p;
	}
	void set_val(int i)
	{
		val=i;
	}

	//
	void set_ptr_val(int val)
	{
		*ptr->ip=val;
	}
	int get_ptr_val() const
	{
		return *ptr->ip;
	}
private:
	int val;
	U_Ptr *ptr;
};</span>

    复制构造函数从形参复制成员并增加使用计数的值。复制构造函数执行完毕后,新创建对象与原有对象指向同一U_Ptr对象,U_Ptr对象的使用计数加1

    析构函数将检查U_Ptr基础对象的使用计数。如果使用计数为0,则这是最后一个指向该U_Ptr对象的HasPtr对象,在这种情况下,HasPtr析构函数删除其U_Ptr指针。删除该指针将引起对U_Ptr析构函数的调用,U_Ptr析构函数删除int基础对象

    赋值操作符在减少左操作数的使用计数之前使rhs的使 用计数加1,从而防止自身赋值。如果左右操作数相同,赋值操作符的效果将是U_Ptr基础对象的使用计数加1之后立即减 1

<span style="font-size:14px;">#include <iostream>
#include "Hasptr.h"
using namespace std;

int main ()
{
	int *obj =new int(12);

	Hasptr ptr1(obj,42);					//注意: obj一定是在堆上申请的内存,不能是在栈上
	//copy 后 &ptr1.ptr==&ptr2.ptr
	cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl;

	Hasptr ptr2(ptr1);
	cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl;


	//因为ptr2是ptr1  copy过来的,而set_ptr_val()函数仅仅修改的是*ptr的值
	//ptr 的地址仍然不变,ptr1和ptr2还是相同
	ptr1.set_ptr_val(34);
	cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl;
	cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl;

	//这里使用了智能指针后, 只是引用计数加1,指针还是同一个,所以值相同
	int *new_obj = new int(44);			//注意: obj一定是在堆上申请的内存,不能是在栈上	
	ptr1.set_ptr(new_obj);
	cout<<ptr1.get_ptr()<<'\t'<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_val()<<endl;
	cout<<ptr2.get_ptr()<<'\t'<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_val()<<endl;


	int *ip=new int (43);
	Hasptr ptr(ip,10);

	/*
	 * 我们不能直接调用 delete ip , 因为这样直接释放掉堆上的内容,导致智能指针ptr
	 * 指向的内容也不复存在
	 */
	//delete ip; //
	ptr.set_ptr_val(0);
	cout<<ptr.get_ptr()<<'\t'<<ptr.get_ptr_val()<<'\t'<<ptr.get_val()<<endl;
	
	system("pause");
	return 0;
}</span>
其结果如下:

从结果中我们可以看到:

1.先创建一个HasPtr 对象ptr1,这会调用构造函数,引用计数初始化为1. 然后调用复制构造函数,可以看到指针的地址是一样的,

只是use变为2.

2.当调用ptr1.set_ptr(new_obj);后 两个对象的指针值都相同(其实是同一个指针),说明它们共享这个指针

3.在return 0;语句之前可以看到,先后调用两个对象的析构函数,分别导致智能指针U_ptr的use为2, 1,然后调用U_ptr的析构函数释放掉内存。

3 定义值型类

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. class HasPtr  
  2. {  
  3. private:  
  4.     HasPtr(const int &p,int i):ptr(new int(p)),val(i) {}  
  5.   
  6.     //复制控制  
  7.     HasPtr(const HasPtr &rhs):ptr(new int(*rhs.ptr)),val(rhs.val) {}  //<复制构造函数中同样申请一个指针
  8.     HasPtr &operator=(const HasPtr &rhs);  
  9.     ~HasPtr()  
  10.     {  
  11.         delete ptr;  
  12.     }  
  13.   
  14.    ........
  15. public:  
  16.     int *ptr;  
  17.     int val;  
  18. };  

  复制构造函数不再复制指针,它将分配一个新的int对象,并初始化该对象以保存与被复制对象相同的值。每个对象都保存属于自己的int值的不同副本。因为每个对象保存自己的副本,所以析构函数将无条件删除指针

    赋值操作符也因而不用分配新对象,它只是必须记得给其指针所指向的对象赋新值,而不是给指针本身赋值:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. HasPtr &HasPtr::operator=(const HasPtr &rhs)  
  2. {  
  3.     *ptr = *rhs.ptr;  //<初始化对象就可以了
  4.     val = rhs.val;  
  5.   
  6.     return *this;  
  7. }  

    即使要将一个对象赋值给它本身,赋值操作符也必须总是保证正确。本例中,即使左右操作数相同,操作本质上也是安全的,因此,不需要显式检查自身赋值。

继续学习文章:http://www.cnblogs.com/TenosDoIt/p/3456704.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值