1. 程式人生 > >C++ 智慧指標(一)

C++ 智慧指標(一)

  •   記憶體安全  

  在C++中,動態記憶體的管理是通過一對運算子來完成的:new,在動態記憶體中為物件分配空間並返回一個指向該物件的指標,我們可以選擇對物件來進行初始化;delete,接收一個動態物件的指標,銷燬該物件,並釋放與之關聯的記憶體。

  動態記憶體的使用很容易出問題,因為確保在正確的時間釋放記憶體是及其困難的。有時我們會忘記釋放記憶體(或程式丟擲異常),在這種情況下就會產生記憶體洩漏;有時在尚有指標引用記憶體的情況下我們就釋放了它,在這種情況下就會產生引用非法記憶體的指標(段錯誤)

  下面寫個Demo測試程式  

#include <memory>
#include <exception>
#define  FLAG 3    //用於編譯不同的程式

class Demo1
{
public:
	Demo1()
	{
		std::cout << "Demo1" << std::endl;
	}

	~Demo1()
	{
		std::cout << "~Demo1" << std::endl;
	}
};

bool throw_test(bool flag)
{
	if (flag)
	{
		throw "throw_test";
	}

	return flag;
}

int main(int argc, char* argv[])
{
	Demo1 *pDemo1 = new Demo1();

	#if (FLAG == 1)
	throw_test(true);  //1.執行這條語句,會列印~Demo1?
	#endif

	#if (FLAG == 2)
	try
	{
		throw_test(true);
	}
	catch (...)	//2....代表捕獲所有異常
	{
		delete pDemo1;	//3.執行這條語句,會列印~Demo1?
		throw;
	}
	#endif

	#if (FLAG == 3)
	delete pDemo1;
	#endif

	return 0;
}

  上述程式結果如下:

   當巨集FLAG為1時,執行throw_test(true),即使程式丟擲異常,它沒有列印~Demo1;

  當巨集FLAG為2時,執行throw_test(true),程式會丟擲異常,之後捕獲到異常列印~Demo1;

  當巨集FLAG為3時,執行delete pDemo1,這是正常操作,程式會呼叫Demo1的解構函式,列印~Demo1;

 

  •  智慧指標

  C++中的智慧指標型別有:auto_ptr,shared_ptr,unique_ptr,weak_ptr(後三者為C++11新增的),它們均為類模板,使用需要包含<memory>標頭檔案

  常規指標帶來的風險

Demo1* pDemo1 = new Demo1();
Demo1* pDemo2;
pDemo2 = pDemo1;
printf("pDemo1:%x pDemo2:%x\n", pDemo1, pDemo2); 

  上面的pDemo1和pDemo2是常規指標,指向同一個物件(淺拷貝),因此列印的地址也是一樣的;請試想一下如果其中一個指標執行了delete操作,那麼另一個指標再執行別的操作會怎樣?程式會發生段錯誤。要避免這個問題,可以用下面這些方案:

  1. 定義賦值運算子函式,進行深拷貝,這樣的操作會使上面的兩個指標不再指向同一個物件,缺點是浪費空間,所以智慧指標都未採用此方案
  2. "獨佔"所指的物件;對於特定的物件,某一時刻只能有一個指標指定一個給定物件,當指標被銷燬時,它所指的物件也被銷燬,這就是用於auto_ptr和uniqiie_ptr 的策略,但unique_ptr的策略更嚴格。
  3. 利用引用計數,建立記錄型的指標;例如,賦值時,計數將加1,而指標過期時,計數將減1。當減為0時才呼叫delete。這是shared_ptr採用的策略。

 

  在C++11中,auto_ptr已棄用;編寫一段測試程式,Demo1類還是使用上面定義的。

int main(int argc, char *argv[])
{
	std::auto_ptr<Demo1> pDemo1(new Demo1);
	std::auto_ptr<Demo1> pDemo2;
	pDemo2 = pDemo1;
	printf("pDemo1:%p pDemo2:%p\n", pDemo1, pDemo2);  //執行到這裡pDemo1會列印什麼?
        return 0;
}    

  上面的程式執行後,列印pDemo1的地址為NULL。具體可以檢視下auto_ptr的賦值運算子的實現是如何的。

  下面這兩張截圖是VS2015下的auto_ptr的賦值運算子的實現;我們可以看到使用賦值運算子時,會呼叫reset函式,這是會將_Myptr的記憶體delete,並將地址置為NULL;如果pDemo1呼叫成員函式,此時會發送什麼?

 

 

 

 

    

 

 

   

  

  auto_ptr存在記憶體崩潰的風險,這個或許就是auto_ptr被C++11棄用的原因吧。

  未完待續...