程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 讀書筆記 effective c++ Item 14 對資源管理類的拷貝行為要謹慎

讀書筆記 effective c++ Item 14 對資源管理類的拷貝行為要謹慎

編輯:關於C++

讀書筆記 effective c++ Item 14 對資源管理類的拷貝行為要謹慎。本站提示廣大學習愛好者:(讀書筆記 effective c++ Item 14 對資源管理類的拷貝行為要謹慎)文章只能為提供參考,不一定能成為您想要的結果。以下是讀書筆記 effective c++ Item 14 對資源管理類的拷貝行為要謹慎正文


1. 自己實現一個資源管理類 

Item 13中介紹了 “資源獲取之時也是初始化之時(RAII)”的概念,這個概念被當作資源管理類的“脊柱“,也描述了auto_ptr和tr1::shared_ptr是如何用堆資源來表現這個概念的。然而並不是所有資源都是在堆上創建的,對於這種資源,像auto_ptr和tr1::shared_ptr這樣的智能指針就不適合當作資源句柄(handle)來使用了。你會發現你時不時的就會需要創建自己的資源管理類。

舉個例子,假設你正在使用C API來操縱Mutex類型的互斥信號量對象,來為函數提供lock和unlock:

1 void lock(Mutex *pm); // lock mutex pointed to by pm
2 
3 void unlock(Mutex *pm); // unlock the mutex

為了確保你不會忘記unlock一個已經加過鎖的Mutex,你需要創建一個類來管理鎖。這樣一個類的基本結構已經由RAII准則表述過了,也就是資源會在執行構造的時候獲取到,在執行析構的時候釋放掉:

 1 class Lock {
 2 
 3 public:
 4 
 5 explicit Lock(Mutex *pm)
 6 
 7 : mutexPtr(pm)
 8 
 9 { lock(mutexPtr); } // acquire resource
10 
11 ~Lock() { unlock(mutexPtr); } // release resource
12 
13 private:
14 
15 Mutex *mutexPtr;
16 
17 };

 

客戶端以傳統的RAII方式來使用鎖:

 1 Mutex m; // define the mutex you need to use
 2 
 3 ...
 4 
 5 { // create block to define critical section
 6 
 7 Lock ml(&m); // lock the mutex
 8 
 9 ... // perform critical section operations
10 
11 } // automatically unlock mutex at end
12 
13 // of block

 

2. 對資源管理類進行拷貝會發生什麼?

這很好,但如果一個鎖對象被拷貝會發生什麼呢?

1 Lock ml1(&m); // lock m
2 
3 Lock ml2(ml1); // copy ml1 to ml2 — what should
4 
5 // happen here?

 

上面是一個更加普通的問題,也是每個RAII類的作者必須面對的:當一個RAII對象被拷貝的時候應該發生什麼呢?大多數情況下,你將會從下面的4種可能中選擇一個:

2.1 禁止拷貝
  • 禁止拷貝。在許多情況下,允許RAII對象被拷貝是沒有意義的。對於一個像Lock的類來說這可能是真的,因為一份同步原語(synchronization primitives)的拷貝很少情況下是有意義的。當一個RAII類的拷貝沒有意義時,你應該禁止它。Item 6解釋了如何可以做到:將拷貝操作聲明稱private。對於Lock來說,可以是下面這個樣子:
1 class Lock: private Uncopyable { // prohibit copying — see
2 
3 public: // Item 6
4 
5 ... // as before
6 
7 };

 

2.2 一份資源,多次引用——使用tr1::shared_ptr
  • 對底層資源進行引用計數。有時候需要保留一個資源直到引用這個資源的最後一個對象被銷毀。在這種情況下,拷貝一個RAII對象應該增加對象引用資源的引用計數。這就是用tr1::shared_ptr進行“拷貝”的含義。

 

     通常情況下,RAII類可以通過包含一個tr1::shared_ptr數據成員來實現引用計數的拷貝行為。舉個例子,如果Lock想使用引用計數,它可以將mutexPtr的類型從Mutex*改為tr1::shared_ptr<Mutex>。不幸的是,tr1::shared_ptr的默認行為是當引用技術為0的時候會刪除它所指向的資源,這不是我們想要的。當我們實現一個Mutex類時,我們只是想unlock,並不想刪除它們。幸運的是,tr1::shared_ptr允許指定自己的刪除器(”deleter”)---一個函數或者函數對象,引用計數為0的時候會自動調用這個對像。(auto_ptr中不存在這個功能,它總是會刪除指針。)這個刪除器是tr1::shared_ptr構造函數的第二個可選參數,所以代碼會是下面這個樣子:

 1 class Lock {
 2 
 3 public:
 4 
 5 explicit Lock(Mutex *pm) // init shared_ptr with the Mutex
 6 
 7 : mutexPtr(pm, unlock) // to point to and the unlock func
 8 
 9 { // as the deleter†
10 
11 lock(mutexPtr.get()); // see Item 15 for info on “get”
12 
13 }
14 
15 private:
16 
17 std::tr1::shared_ptr<Mutex> mutexPtr; // use shared_ptr
18 
19 }; // instead of raw pointer

 

注意在這個例子中,Lock類不再聲明析構函數。因為沒有必要了。Item 5 解釋到一個類的析構函數(無論是編譯器生成的還是用戶定義的)會自動調用類中的非靜態數據成員的析構函數。在這個例子中,非靜態數據成員為mutexPtr。但是在mutex的引用計數為0的時候其的析構函數會自動調用tr1::shared_ptr的刪除器—也即是unlock。(人們在看到類的源碼的時候如果有一行注釋來說明你沒有忘記析構,你只是使用了編譯器默認生成的析構函數,他們會很感激的。)

2.3 一份資源,多次拷貝——深拷貝
  • 拷貝底層的資源。有時你可以擁有一個資源盡可能多的拷貝,你需要一個資源管理類的唯一原因是能夠確保資源被使用完畢後能夠被釋放掉。這種情況下,拷貝一個資源管理對象應該同時拷貝他所包裹(wraps)的資源。也就是拷貝一個資源管理類對象需要執行“深拷貝”。

有一些標准string類型的實現中包含了指向堆內存的指針,組成string的字符會保存在這塊內存中。當一個string對象被拷貝的時候,會同時拷貝指針和指針指向的內存。這樣的string展示出來的是深拷貝。

2.4 一份資源,一次引用,轉移所有權——使用auto_ptr
  • 轉移底層資源的所有權。在很少的場合,你可能需要確保只有一個RAII對象指向一個原生(raw)資源,所以當RAII對象被拷貝的時候,資源的擁有權從被拷貝對象轉移到了拷貝到的對象。正如Item 13所解釋的,這是使用auto_ptr進行拷貝的含義。 

拷貝函數可能由編譯器生成,所以除非編譯器生成版本能夠做到你想要的(Item 5解釋了默認版本的行為),否則你需要自己實現它們。一些情況下你可能想支持這些函數的一般版本。這些版本在Item 45進行描述。

3. 總結
  • 拷貝一個RAII對象需要拷貝他所管理的資源,因此資源的拷貝行為決定了RAII對象的拷貝行為。
  • 普通RAII類的拷貝行為是禁止拷貝,執行引用計數,但其他拷貝行為也是可以實現的。
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved