iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0
自我挑戰組

Effective C++ 讀書筆記系列 第 20

[Day 20] Think carefully about copying behavior in resource-managing classes

  • 分享至 

  • xImage
  •  

前言

今天繼續來看resource managing系列的第二個守則,看看開始用物件來管理resource後,還有哪些注意事項。

更多的RAII

今天的守則是:

Think carefully about copying behavior in resource-managing classes

昨天有提到可以利用smart pointer來管理heap-based的資源,不過,如第三章前言有提到的,還有一些資源是heap-based這些memory以外的東西。例如,當我們用到mutex objects:

void lock(Mutex *pm);
void unlock(Mutext *pm);

為了確保不會忘記unlock Mutex object,我們也可以用一個class來管理這些locks資源。我們一樣遵循RAII原則來管理這個resource,讓這個lock被物件管理,並在destroy的時候自動unlock:

class Lock
{
public:
    explicit Lock(Mutex *pm): mutexPtr(pm)
    {
        lock(mutextPtr);
    }
    
    ~Lock()
    {
        unclock(mutextPtr);
    }
private:
    Mutex *mutexPtr;
}

clients在用的時候就可以這樣用:

Mutex m;
...
{
   Lock ml(&m);
   ...
}

以上看起來沒什麼問題,不過當Lock這個物件被copy的時候,會發生什麼事呢?

Lock ml1(&m)l
Lock ml2(ml1);

其實所有RAII class都會面對到這個問題,當RAII物件被複製的時候,我們應該怎麼做?

處理RAII複製的四種方法

1. Prohibit copying─禁止複製

很多情況下RAII物件其實不該能被複製,例如lock的情形,複製同步處理物件沒有意義;而此時如果有禁止,就可以參考第六條守則─[Day 10] Explicitly disallow the use of compiler-generated functions you do not want,去把不想要有的copying functions ban掉。

Lock(const Lock&)= delete;
Lock& operator=(const Lock&)= delete;

2. Reference-count the underlying resource─參考計數

有時候我們對資源的管理,是預期資源直到最後一個有用到它的物件被destroy後才跟著釋放,而在這個情形下,每copy一次等於是多一個用到它的物件,所以參考計數要+1,這也是shared_ptr採用的概念。
而如果要採用這種reference-counting copying的方式,我們通常可以使用shared_ptr來做。例如當我們想要讓我們的Lock採用這種方式,我們可以把mutexPtrMutex*轉成shared_ptr<Mutex>。不過shared_ptr的預設destructor行為是delete,這邊我們期望的release行為則是unlock,需要做一些處理─ shared_ptr有開放 deleter 的參數,讓你可以傳入想要在ref count歸0時做的行為,可以這樣寫:

class Lock
{
public:
    explicit Lock(Mutex *pm): mutexPtr(pm, unlock)
    {
        lock(mutexPtr.get());
    }
private:
    std::shared_ptr<Mutex> mutexPtr;
}

值得注意的是,採用此寫法之後,我們沒有再定義Lock的destructor了。why?因為unlock這個行為會在shared_ptr ref count歸0 的時候自動去call,因此就不需要了。

3. Copy the underlying resource─一起複製(deep copy)

有時候我們的設計是想要讓resource可以被無限複製,而resource-managing class只是要確保這個resource被用完後又被正確release,這情況下,copy resource managing物件就需要同步copy它管理的resource,那就是做deep copy的概念。
std::string就是其中一個範例。這個物件包含了指向heap memory的pointer,而當string被複製的時候,這個copy就包含了pointer跟它指向的memory,就是做了deep copy。

4. Transfer ownership of the underlying resource─責任轉移

還有一個稀有的情況,就是需要確保只有一個RAII物件在管理一個resource,所以當它被copy的時候,也需要同步把ownership轉給copy的物件。這就像是auto_ptr的行為─只有一個人能指向它,所以copy過去之後原本的會被設為null(在unique_ptr的情況下,我們可以用move來轉移ownership)。

如第五個守則所說([Day 9] Know what functions C++ silently writes and calls),copying functions會自動由compiler產生,而在resource manaing object的case中,更需要分外注意default產生的functions是不是我們預期的,若不是要記得按照上面幾種方式去處理。

總結

貼心重點提醒:

  • Copying an RAII object entails copying the resource it manages, so the copying behavior of the resource determines the copying behavior of the RAII obejct.
  • Common RAII class copying behvaviors are disallowing copying and performing reference counting, but other behaviors are possible.

第一就是切合主題,RAII的copy要注意它所管理的resource是怎麼被copy的;第二則是通常會直接把resource managing object的copy行為ban掉,但仍然有其他處理方式。

參考資料


上一篇
[Day 19] Use objects to manage resources
下一篇
[Day 21] Provide access to raw resources in resource-managing classes
系列文
Effective C++ 讀書筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言