天天看點

auto_ptr的相關使用

auto_ptr簡介

auto_ptr是STL中智能指針的一員,與C++98引入,定義在頭檔案**< memory >**中。

正如前一篇所說,其功能和用法類似于unique_ptr,由new expression獲得對象;

當aut_ptr對象銷毀時,它說管理的對象也會自動被delete掉。

auto_ptr被unique_ptr替換的原因

(1)出于安全考慮

對于指派語句:

auto_ptr< string> ps (new string ("I reigned lonely as a cloud.”);
auto_ptr<string> vocation; 
vocaticn = ps;
           

對于以上的指派語句,如果ps和vocation是正常指針,則:

  • 這兩個指針将會指向同一個string對象。

而出現了這種情況,就會造成錯誤,因為程式将會試圖删除一個對象兩次,一次在ps過期時,另一次則是在vocation過期時。

想要避免這樣的情況發生,可取的方法是:

(1)定義指派運算符,使之執行深複制。這樣兩個指針指向的就是兩個不同的對象,其中的一個對象是另外一個對象的副本。雖然可行,但是會浪費空間,是以,智能指針并沒有采取這種方案。

(2)建立 所有權(ownership) 的概念。對于特定的對象,隻能有一個隻能指針可擁有,這樣隻有擁有對象的智能指針的析構函數會删除對象。然後,讓指派操作轉讓這樣的所有權。這是用于auto_ptr和unique_ptr的政策,但是unique_ptr的政策更嚴格。

(3)建立智能更高的指針,跟蹤引用特定對象的智能指針數。這稱為引用計數。例如,指派時,計數将加1,而指針過期時,計數将減1。當減為0時才調用delete。這是shared_ptr采用的政策。

同樣的政策也适用于複制構造函數,即auto_ptr vocation(ps)時也需要上面的政策。

#include <iostream>
#include <string>
#include <memory>
using namespace std;

int main()
{
	auto_ptr<string> films[5] ={
	auto_ptr<string> (new string("Fowl Balls")),
	auto_ptr<string> (new string("Duck Walks")),
	auto_ptr<string> (new string("Chicken Runs")),
	auto_ptr<string> (new string("Turkey Errors")),
	auto_ptr<string> (new string("Goose Eggs"))
	};
    auto_ptr<string> pwin;

	// films[2] loses ownership! 将所有權從films[2]轉讓給pwin,此時films[2]不再引用該字元串進而變成空指針
    pwin = films[2]; 

	cout << "The nominees for best avian baseballl film are ";
	for(int i = 0; i < 5; ++i)
	{
		cout << *films[i] << endl;
	}
 	cout << "The winner is " << *pwin << endl;
	return 0;
}
           

在上面的程式中,會發生崩潰,原因如上所說,films[2]已經是空指針了,下面輸出通路空指針自然會發生崩潰。

然而,如果将auto_ptr替換為shared_ptr或者unique_ptr後,程式就不會發生崩潰了,具體情況如下:

  • 當使用shared_ptr時,運作時正常的,因為shared_ptr采用引用計數,是以pwin和films[2]所指的都是同一塊記憶體,在釋放空間時,因為事先要判斷引用計數值的大小,于是不會出現多次删除一個對象的錯誤。
  • 當使用unique_ptr時,會發生編譯錯誤,和quto_ptr類似,unique_ptr也采用所有權模型,但是不同之處在于:程式不會等到運作階段崩潰,而在編譯期因下述代碼出現錯誤:
unique_ptr<string> pwin;
pwin = films[2];
           

指導你發現潛在的記憶體錯誤。這就是為何要摒棄auto_ptr的原因,一句話總結就是:避免因潛在的記憶體問題導緻程式崩潰。

由上面可見,unique_ptr比auto_ptr更安全:

  • 因為auto_ptr有拷貝語義,拷貝之後原對象會變得無效,再次通路原對象會導緻程式崩潰;
  • 而unique_ptr則禁止了拷貝語義,但是提供了移動語義,即可以使用std::move()進行控制權限的轉移:
unique_ptr<string> upt(new string("lvlv"));
unique_ptr<string> upt1(upt);	//編譯出錯!已禁止拷貝
unique_ptr<string> upt1=upt;	//編譯出錯!已禁止拷貝
unique_ptr<string> upt1=std::move(upt);  //控制權限轉移

auto_ptr<string> apt(new string("lvlv"));
auto_ptr<string> apt1(apt);	//編譯通過
auto_ptr<string> apt1=apt;	//編譯通過
           

在上面的代碼中,使用std::move将unique_ptr的控制權限轉移後,就不能在通過unique_ptr來通路和控制資源了,否則同樣會出現程式的崩潰。

在使用unique_ptr通路資源之前,可以使用成員函數get()進行判空操作。

unique_ptr<string> upt1=std::move(upt);  		//控制權限轉移
if(upt.get()!=nullptr)					//判空操作更安全
{
	//do something
}
           

(2)unique_ptr不僅安全,而且靈活

如果unique_ptr是個臨時右值,編譯器允許拷貝語義。

unique_ptr<string> demo(const char * s)
{
    unique_ptr<string> temp (new string (s)); 
    return temp;
}

//假設編寫了如下代碼:
unique_ptr<string> ps;
ps = demo('Uniquely special");
           

在上面的代碼中,demo()傳回一個臨時unique_ptr,然後ps接管了臨時變量unique_ptr所管理的資源,而傳回時時臨時的unique_ptr會被銷毀,也就是說沒有機會使用unique_ptr來通路無效的資料。

換句話來說,這種指派是不會出現任何問題的,即沒有理由禁止這種指派。實際上,編譯器确實允許這種指派。相對于auto_ptr任何情況下都允許拷貝語義,這正是unique_ptr更加靈活聰明的地方。

(3)擴充auto_ptr不能完成的功能

(1)unique_ptr可放在容器中,彌補了auto_ptr不能作為容器元素的缺點。

//方法1
vector<unique_ptr<string>> vs { new string{“Doug”}, new string{“Adams”} };  

//方法2
vector<unique_ptr<string>>v;
unique_ptr<string> p1(new string("abc"));  
           

(2)管理動态數組,因為unique_ptr有unique_ptr<X[]>重載版本,銷毀動态對象時調用delete[]。

unique_ptr<int[]> p (new int[3]{1,2,3});  
p[0] = 0;// 重載了operator[]
           

(3)自定義資源删除操作(Deleter)。unique_ptr預設的資源删除操作是delete/delete[],若需要,可以進行自定義:

void end_connection(connection *p) { disconnect(*p); } //資源清理函數

//資源清理器的“類型” 
unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);// 傳入函數名,會自動轉換為函數指針 
           

綜上所述,基于unique_ptr的安全性和擴充的功能,unique_ptr成功的将auto_ptr取而代之。

參考文章:

STL四種智能指針