Item 15: 在 resource-managing classes(資源管理類)中提供對 raw resources(裸資源)的訪問
作者:Scott Meyers
譯者:fatalerror99 (iTePub's Nirvana)
發布:http://blog.csdn.net/fatalerror99/
resource-managing classes(資源管理類)真是太棒了。他們是你防禦 resource leaks(資源泄漏)的堡壘,沒有這樣的泄漏是設計良好的系統的基本特徵。在一個完美的世界中,你可以在所有你與資源的互動中依賴這樣的 classes,從來不需要因為對 raw resources(裸資源)的直接存取而玷汙你的手。但是這個世界並不完美,很多 APIs 直接涉及資源,所以除非你打算堅決放棄使用這樣的 APIs(很少成為現實的事),否則,你只得經常繞過 resource-managing object(資源管理對象)而直接處理 raw resources(裸資源)。
例如,Item 13 介紹的使用類似 auto_ptr 或 tr1::shared_ptr 這樣的 smart pointers(智能指標)來持有對類似 createInvestment 這樣的 factory function(工廠函數)的調用的結果想法:
std::tr1::shared_ptr<Investment> pInv(createInvestment()); // from Item 13
假設你打算使用的一個函數與 Investment objects 一起工作時是這樣的:
int daysHeld(const Investment *pi); // return number of days
// investment has been held
你打算像這樣調用它,
int days = daysHeld(pInv); // error!
但是這代碼不能編譯:daysHeld 要求一個裸的 Investment* 指標,但是你傳給它一個類型 tr1::shared_ptr<Investment> 的 object。
你需要一個將 RAII class(當前情況下是 tr1::shared_ptr)的 object 轉化為它所包含的 raw resource(裸資源)(例如,底層的 Investment*)的方法。有兩個常規方法可以做到:explicit conversion(顯式轉換)和 implicit conversion(隱式轉換)。
tr1::shared_ptr 和 auto_ptr 都提供一個 get member function(成員函數)用於實行一次 explicit conversion(顯示轉換),也就是,用於返回 smart pointer object(智能指標對象)內部的raw pointer(裸指標)(的一個拷貝):
int days = daysHeld(pInv.get()); // fine, passes the raw pointer
// in pInv to daysHeld
就像實際上的所有 smart pointer classes(智能指標類)一樣,tr1::shared_ptr 和 auto_ptr 也都重載了 pointer dereferencing operators(指標解引用操作符)(operator-> 和 operator*),而這樣就允許到底層 raw pointers(裸指標)的 implicit conversion(隱式轉換):
class Investment { // root class for a hierarchy
public: // of investment types
bool isTaxFree() const;
...
};
Investment* createInvestment(); // factory function
std::tr1::shared_ptr<Investment> // have tr1::shared_ptr
pi1(createInvestment()); // manage a resource
bool taxable1 = !(pi1->isTaxFree()); // access resource
// via operator->
...
std::auto_ptr<Investment> pi2(createInvestment()); // have auto_ptr
// manage a
// resource
bool taxable2 = !((*pi2).isTaxFree()); // access resource
// via operator*
...
因為有些時候有必要取得 RAII object 內部的 raw resource(裸資源),所以一些 RAII class 的設計者就通過提供一個 implicit conversion function(隱式轉換函式)來給刹車抹油。例如,考慮以下這個 RAII class,它要為一個 C API 提供原始狀態的 fonts(字型):
FontHandle getFont(); // from C API—params omitted
// for simplicity
void releaseFont(FontHandle fh); // from the same C API
class Font { // RAII class
public:
explicit Font(FontHandle fh) // acquire resource;
: f(fh) // use pass-by-value, because the
{} // C API does
~Font() { releaseFont(f); } // release resource
private:
FontHandle f; // the raw font resource
};
假設有一個巨大的 font-related(字型相關)的 C API 只能與 FontHandle 打交道,這就頻繁地需要將 Font objects 轉換為 FontHandles。Font class 可能提供一個像 get 這樣的 explicit conversion function(顯示轉換函式):
class Font {
public:
...
FontHandle get() const { return f; } // explicit conversion function
...
};
不幸的是,這就要求客戶每次想要與這個 API 打交道時都要調用 get:
void changeFontSize(FontHandle f, int newSize); // from the C API
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize); // explicitly convert
// Font to FontHandle
一些程式員可能發現對這個轉換的顯式請求的需要令人鬱悶到足以避免使用這個 class。反過來,這又增加了 leaking fonts(字型泄漏)的機會,而這每一件事都是通過設計 Font class 來避免的
可選擇的辦法是為 Font 提供一個到它的 FontHandle 的 implicit conversion function(隱式轉換函式):
class Font {
public:
...
operator FontHandle() const { return f; } // implicit conversion function
...
};
這樣就可以使對 C API 的調用簡單而自然:
Font f(getFont());
int newFontSize;
...
changeFontSize(f, newFontSize); // implicitly convert Font
// to FontHandle
不利的方面是 implicit conversions(隱式轉換)增加了錯誤的機會。例如,一個客戶可能會在想要使用 Font 的地方意外地建立一個 FontHandle:
Font f1(getFont());
...
FontHandle f2 = f1; // oops! meant to copy a Font
// object, but instead implicitly
// converted f1 into its underlying
// FontHandle, then copied that
現在,程式有了一個被 Font object f1 管理的 FontHandle,但是這個 FontHandle 也能通過直接使用 f2 來加以利用。這幾乎絕對不會是什麼好事。例如,當 f1 被銷毀,字型將被釋放,f2 則被 dangle(懸掛)。
關於是否提供從一個 RAII class 到它的底層資源的 explicit conversion(顯式轉換)(例如,通過一個 get member function(成員函數))或者是否允許 implicit conversion(隱式轉換)的決定,要依靠 RAII class 被設計履行的具體任務和它被計劃使用的情境而做出。最好的設計很可能就是堅持 Item 18 的建議(使 interfaces(介面)易於正確使用,而難以錯誤使用)的那一個。通常,一個類似 get 的 explicit conversion function(顯式轉換函式)是更可取的方式,因為它將意外的 type conversions(類型轉換)的機會減到最少。然而有時,通過 implicit type conversions(隱式類型轉換)提高使用的自然性將使天平向那個方向傾斜。
你可能已經意識到,函數返回一個 RAII class 內部的 raw resource(裸資源)違背了 encapsulation(封裝性)。這是正確的,但這並非像它開始看上去那樣是個設計的禍患。RAII classes 的存在並非為了封裝什麼東西;它的存在是為了確保一個特定的動作—— resource release(資源釋放)——的發生。如果你希望,資源的 encapsulation(封裝性)的地位也可以提高到這個主要功能之上,但這並非必需。此外,一些 RAII classes 將實現的嚴格封裝性和底層資源的非常寬鬆的封裝性結合在一起。例如,tr1::shared_ptr 整體封裝了它的 reference-counting machinery(引用計數機制),但它依然提供對它所包含的 raw pointer(裸指標)的簡單訪問。就像大多數設計良好的 classes,它隱藏了客戶不需要看到的,但它也讓客戶的確需要訪問的那些東西可以利用。
Things to Remember
- APIs 經常需要訪問 raw resources(裸資源),所以每一個 RAII class 都應該提供一種方法以取得它所管理的資源。
- 訪問可以經由 explicit conversion(顯式轉換)或者 implicit conversion(隱式轉換)進行。通常,explicit conversion(顯式轉換)更安全,而 implicit conversion(隱式轉換)對客戶來說更方便。