正文

回到頂部

1. 自己實現(xiàn)一個資源管理類 

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

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

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

為了確保你不會忘記unlock一個已經(jīng)加過鎖的Mutex,你需要創(chuàng)建一個類來管理鎖。這樣一個類的基本結(jié)構(gòu)已經(jīng)由RAII準(zhǔn)則表述過了,也就是資源會在執(zhí)行構(gòu)造的時候獲取到,在執(zhí)行析構(gòu)的時候釋放掉

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

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

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

 

客戶端以傳統(tǒng)的RAII方式來使用鎖:

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

 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 operations10 11 } // automatically unlock mutex at end12 13 // of block

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

 

回到頂部

2. 對資源管理類進行拷貝會發(fā)生什么?

這很好,但如果一個鎖對象被拷貝會發(fā)生什么呢?

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

 

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

2.1 禁止拷貝

  • 禁止拷貝。在許多情況下,允許RAII對象被拷貝是沒有意義的。對于一個像Lock的類來說這可能是真的,因為一份同步原語(synchronization primitives)的拷貝很少情況下是有意義的。當(dāng)一個RAII類的拷貝沒有意義時,你應(yīng)該禁止它。Item 6解釋了如何可以做到:將拷貝操作聲明稱private。對于Lock來說,可以是下面這個樣子:

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

1 class Lock: private Uncopyable { // prohibit copying — see2 3 public: // Item 64 5 ... // as before6 7 };

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

 

2.2 一份資源,多次引用——使用tr1::shared_ptr

  • 對底層資源進行引用計數(shù)。有時候需要保留一個資源直到引用這個資源的最后一個對象被銷毀。在這種情況下,拷貝一個RAII對象應(yīng)該增加對象引用資源的引用計數(shù)。這就是用tr1::shared_ptr進行“拷貝”的含義。

 

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

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

 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_ptr18 19 }; // instead of raw pointer

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

 

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

2.3 一份資源,多次拷貝——深拷貝

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

有一些標(biāo)準(zhǔn)string類型的實現(xiàn)中包含了指向堆內(nèi)存的指針,組成string的字符會保存在這塊內(nèi)存中。當(dāng)一個string對象被拷貝的時候,會同時拷貝指針和指針指向的內(nèi)存。這樣的string展示出來的是深拷貝。

2.4 一份資源,一次引用,轉(zhuǎn)移所有權(quán)——使用auto_ptr

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

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

回到頂部

3. 總結(jié)

  • 拷貝一個RAII對象需要拷貝他所管理的資源,因此資源的拷貝行為決定了RAII對象的拷貝行為。

  • 普通RAII類的拷貝行為是禁止拷貝,執(zhí)行引用計數(shù),但其他拷貝行為也是可以實現(xiàn)的。