天天看點

智能指針學習

包含指針的類需要特别注意複制控制,原因是複制指針時隻是複制了指針中的位址,而不會複制指針指向的對象!

    将一個指針複制到另一個指針時,兩個指針指向同一對象。當兩個指針指向同一對象時,可能使用任一指針改變基礎對象。類似地,很可能一個指針删除了一對象時,另一指針的使用者還認為基礎對象仍然存在。指針成員預設具有與指針對象同樣的行為。

大多數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

智能指針學習
智能指針學習
  1. class HasPtr  
  2. {  
  3. private:  
  4.     HasPtr(const int &p,int i):ptr(new int(p)),val(i) {}  
  5.     //複制控制  
  6.     HasPtr(const HasPtr &rhs):ptr(new int(*rhs.ptr)),val(rhs.val) {}  //<複制構造函數中同樣申請一個指針
  7.     HasPtr &operator=(const HasPtr &rhs);  
  8.     ~HasPtr()  
  9.     {  
  10.         delete ptr;  
  11.     }  
  12.    ........
  13. public:  
  14.     int *ptr;  
  15.     int val;  
  16. };  

  複制構造函數不再複制指針,它将配置設定一個新的int對象,并初始化該對象以儲存與被複制對象相同的值。每個對象都儲存屬于自己的int值的不同副本。因為每個對象儲存自己的副本,是以析構函數将無條件删除指針。

    指派操作符也因而不用配置設定新對象,它隻是必須記得給其指針所指向的對象賦新值,而不是給指針本身指派:

[cpp]  view plain copy

智能指針學習
智能指針學習
  1. HasPtr &HasPtr::operator=(const HasPtr &rhs)  
  2. {  
  3.     *ptr = *rhs.ptr;  //<初始化對象就可以了
  4.     val = rhs.val;  
  5.     return *this;  
  6. }  

    即使要将一個對象指派給它本身,指派操作符也必須總是保證正确。本例中,即使左右操作數相同,操作本質上也是安全的,是以,不需要顯式檢查自身指派。

繼續學習文章:http://www.cnblogs.com/TenosDoIt/p/3456704.html

繼續閱讀