正文

回到頂部

1.為什么需要訪問資源管理類中的原生資源 

資源管理類是很奇妙的。它們是防止資源泄漏的堡壘,沒有資源泄漏發(fā)生是設(shè)計良好的系統(tǒng)的一個基本特征。在一個完美的世界中,你需要依賴這樣的類來同資源進(jìn)行交互,絕不要直接訪問原生(raw)資源而玷污你的雙手。但是世界不是完美的,許多API會直接引用資源,所以除非你放棄使用這樣的API(這是不實際的想法),你將會繞開資源管理類而時不時的處理原生資源。

回到頂部

2. 如何獲取原生資源——通過顯示轉(zhuǎn)換和隱式轉(zhuǎn)換

2.1 一個例子

舉個例子,Item 13中介紹了使用像auto_ptr或者tr1::shared_ptr這樣的智能指針來存放調(diào)用createInvestment工廠函數(shù)的返回結(jié)果:

1 std::tr1::shared_ptr<Investment> pInv(createInvestment()); // from Item 13

 

假設(shè)你想使用一個同Investment對象一起工作的函數(shù),如下:

1 int daysHeld(const Investment *pi); // return number of days2 3 // investment has been held

你會像下面這樣調(diào)用它:

1 int days = daysHeld(pInv); // error!

 

代碼將不能通過編譯:dayHeld想要使用一個原生Investment*指針,你卻傳遞了一個tr1::shared_ptr<Investment>類型的對象。

 

你需要一種方法將一個RAII類對象(在這個例子中是tr1::shared_ptr)轉(zhuǎn)換成它所包含的原生資源類型。有兩種常見的方法來實現(xiàn)它:顯示轉(zhuǎn)換和隱式轉(zhuǎn)換。

2.2 使用智能指針的get進(jìn)行顯示轉(zhuǎn)換

Tr1::shared_ptr和auto_ptr都提供了一個get成員函數(shù)來執(zhí)行顯示轉(zhuǎn)換,也就是返回智能指針對象內(nèi)部的原生指針:

1 int days = daysHeld(pInv.get()); // fine, passes the raw pointer2 3 // in pInv to daysHeld

 

2.3 使用智能指針的解引用進(jìn)行隱式轉(zhuǎn)換

事實上像所有的智能指針一樣,tr1::shared_ptr和auto_ptr也重載了指針的解引用運算符(operator->和operator*),這就允許將其隱式的轉(zhuǎn)換成底層原生指針: 

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 1 class Investment { // root class for a hierarchy 2  3 public: // of investment types 4  5 bool isTaxFree() const; 6  7 ... 8  9 };10 11 Investment* createInvestment(); // factory function12 13 std::tr1::shared_ptr<Investment> // have tr1::shared_ptr14 15 pi1(createInvestment()); // manage a resource16 17 bool taxable1 = !(pi1->isTaxFree()); // access resource18 19 // via operator->20 21 ...22 23 std::auto_ptr<Investment> pi2(createInvestment()); // have auto_ptr24 25 // manage a26 27 // resource28 29 bool taxable2 = !((*pi2).isTaxFree()); // access resource30 31 // via operator*

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 

2.3 自己實現(xiàn)get進(jìn)行顯示轉(zhuǎn)換

因為有時候獲取RAII對象中的原生資源是必要的,一些RAII類的設(shè)計者通過提供一個隱式轉(zhuǎn)換函數(shù)來順利達(dá)到此目的。舉個例子,考慮下面的字體RAII類,字體對于C API來說是原生數(shù)據(jù)結(jié)構(gòu):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 1 FontHandle getFont(); // from C API — params omitted 2  3 // for simplicity 4  5 void releaseFont(FontHandle fh); // from the same C API 6  7 class Font { // RAII class 8  9 public:10 11 explicit Font(FontHandle fh) // acquire resource;12 13 : f(fh) // use pass-by-value, because the14 15 {} // C API does16 17 ~Font() { releaseFont(f ); } // release resource18 19 ... // handle copying (see Item 14)20 21 private:22 23 FontHandle f; // the raw font resource24 25 };

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 

假設(shè)有大量的字體相關(guān)的C API用于處理FontHandles,因此會有頻繁的需求將Font對象轉(zhuǎn)換成FontHandles對象。Font類可以提供一個顯示的轉(zhuǎn)換函數(shù),比如說:get:

 

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 1 class Font { 2  3 public: 4  5 ... 6  7 FontHandle get() const { return f; } // explicit conversion function 8  9 ...10 11 };

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 

不幸的是,如果它們想同API進(jìn)行通訊,每次都需要調(diào)用get函數(shù):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 1 void changeFontSize(FontHandle f, int newSize); // from the C API 2  3 Font f(getFont()); 4  5 int newFontSize; 6  7 ... 8  9 changeFontSize(f.get(), newFontSize); // explicitly convert10 11 // Font to FontHandle

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

一些程序員發(fā)現(xiàn)顯示請求這些轉(zhuǎn)換是如此令人不愉快以至于不想使用RAII類。但是這會增加泄漏字體資源的機(jī)會,這正是設(shè)計Font類要預(yù)防的事情。

 

2.3 自己實現(xiàn)operator() 進(jìn)行隱式轉(zhuǎn)換

 一種替代的方法是讓Font提供一個隱式轉(zhuǎn)換到FontHandle的函數(shù):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 1 class Font { 2  3 public: 4  5 ... 6  7 operator FontHandle() const // implicit conversion function 8 { return f; } 9 10 ...11 12 };

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 

這會使C API的調(diào)用變得容易并且很自然:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

1 Font f(getFont());2 3 int newFontSize;4 5 ...6 7 changeFontSize(f, newFontSize); // implicitly convert Font8 9 // to FontHandle

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 

缺點是隱式轉(zhuǎn)換增加了出錯的機(jī)會。舉個例子,客戶端本來想要一個Font卻創(chuàng)建了一個FontHandle:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 1 Font f1(getFont()); 2  3 ... 4  5 FontHandle f2 = f1; // oops! meant to copy a Font 6  7 // object, but instead implicitly 8  9 // converted f1 into its underlying10 11 // FontHandle, then copied that

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

 

現(xiàn)在程序擁有一個被Font對象 f1管理的FontHandle,但是直接使用f2也可以獲得這個FontHandle。這就不好了。例如:當(dāng)f1被銷毀,字體資源被釋放,f2就變成了懸掛指針。

回到頂部

3.隱式轉(zhuǎn)換和顯示轉(zhuǎn)換如何選擇?

提供從RAII類對象到底層資源的顯示轉(zhuǎn)換(通過一個get成員函數(shù))還是提供隱式轉(zhuǎn)換依賴于設(shè)計出來的RAII類需要執(zhí)行的特殊任務(wù)以及使用的場景。最好的設(shè)計看上去要遵守Item 18的建議:使接口容易被正確使用,很難被誤用。通常情況下,像get一樣的顯示轉(zhuǎn)換函數(shù)會是更好的選擇,因為它減少了類型誤轉(zhuǎn)換的機(jī)會。然而有時候,使用隱式類型轉(zhuǎn)換的自然特性會使局面發(fā)生扭轉(zhuǎn)。

 

回到頂部

4.訪問原生資源和封裝背道而馳?

函數(shù)返回一個RAII類中的原生資源同封裝是背道而馳的,這已經(jīng)發(fā)生了。這不是設(shè)計的災(zāi)難,RAII類的存在不是用來封裝一些東西;他們的存在是用來保證資源的釋放會發(fā)生。如果需要,資源封裝可以在這個基本功能之上進(jìn)行實現(xiàn),但這不是必要的。此外,一些RAII類將實現(xiàn)的真正封裝同底層資源非常松散的封裝組合到一塊。舉個例子:tr1::shared_ptr封裝了所有的引用計數(shù),但是仍然可以非常容易的訪問它所包含的原生指針。像一些設(shè)計良好的類,它隱藏了客戶沒有必要看到的東西,但是它提供了客戶端確實需要訪問的東西。

回到頂部

5.總結(jié)

  • API通常需要訪問原生資源,所以每個RAII類應(yīng)該提供一個獲得它所管理的原生資源的方法。

  • 訪問原生資源可以通過顯式轉(zhuǎn)換或者隱式轉(zhuǎn)換來達(dá)到。一般情況下,顯示轉(zhuǎn)換更加安全,隱式轉(zhuǎn)換對客戶端來說更加方便。