RAII(Resource Aquisition is Initialization)技術(shù)是用對(duì)象來(lái)管理資源的一種技術(shù),資源可以指內(nèi)存凑队、socket伦乔、IPC 等。
用 RAII 管理鎖資源
這個(gè)概念比較抽象企巢,我們還是從具體的例子中學(xué)習(xí),一般我們這樣使用互斥鎖:
pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;
void functionA() {
pthread_mutex_lock(&mu);
// ... 操作共享資源
pthread_mutex_unlock(&mu);
}
即让蕾,我們?cè)谑褂霉蚕碣Y源之前通過(guò) pthread_mutex_lock
加鎖浪规,并在使用完資源后,通過(guò) pthread_mutex_unlock
解鎖探孝,但這種代碼隱患極大笋婿,因?yàn)槟悴荒鼙WC鎖一定會(huì)釋放,例如在使用資源的時(shí)候可能拋出異常顿颅,那么這個(gè)鎖就永遠(yuǎn)得不到釋放缸濒,那有什么辦法可以讓鎖一定釋放,甚至自動(dòng)釋放呢粱腻?那就要用到今天提到的 RAII 技術(shù):我們用對(duì)象來(lái)管理鎖庇配,對(duì)象存儲(chǔ)在棧中,利用代碼塊在退出時(shí)會(huì)自動(dòng)釋放棧資源的特性绍些,鎖也會(huì)自動(dòng)得到釋放讨永,如下面的代碼:
#include <pthread.h>
pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;
class Lock {
private:
pthread_mutex_t* m_pm;
public:
explicit Lock(pthread_mutex_t* pm) { pthread_mutex_lock(pm); m_pm = pm; }
~Lock() { pthread_mutex_unlock(m_pm); }
};
void functionA() {
Lock mylock(&mu);
// ... 操作共享資源
// mutex會(huì)在函數(shù)退出時(shí)自動(dòng)釋放
}
上面代碼中,Lock
構(gòu)造函數(shù)接受一個(gè) mutex 指針遇革,同時(shí)會(huì)調(diào)用 pthread_mutex_lock
加鎖,并會(huì)在該對(duì)象被析構(gòu)時(shí)揭糕,調(diào)用 pthread_mutex_unlock
解鎖萝快,這就做到了對(duì)象創(chuàng)建時(shí)加鎖,釋放時(shí)解鎖的效果著角,如果我們把這個(gè)對(duì)象放到棧中揪漩,則鎖資源也會(huì)隨著該對(duì)象在棧中的生命周期進(jìn)行自動(dòng)的加鎖和解鎖,函數(shù)或者代碼塊都可以構(gòu)造這樣的上下文吏口。而這種用對(duì)象來(lái)管理資源的方式奄容,就是我們開篇所說(shuō)的 RAII。
shared_ptr 也是一種 RAII
另一個(gè)典型的使用 RAII 技術(shù)的例子是 std::shared_ptr
产徊,我們通過(guò) shared_ptr
來(lái)管理資源——一般是堆中申請(qǐng)的對(duì)象昂勒,shared_ptr
通過(guò)引用計(jì)數(shù)來(lái)管理指針對(duì)象,我們對(duì) shared_ptr
進(jìn)行復(fù)制舟铜,引用計(jì)數(shù)就加 1戈盈,相反,如果減少一個(gè) shared_ptr
,引用計(jì)數(shù)就減 1塘娶,當(dāng)引用計(jì)數(shù)減到 0 時(shí)归斤,會(huì)自動(dòng)調(diào)用 delete
釋放指針對(duì)象,下面的代碼使用了一個(gè) pd
智能指針來(lái)管理 dog
對(duì)象刁岸,當(dāng) pd
退出作用域脏里,如果沒(méi)有額外的智能指針引用 dog
,則 dog
會(huì)被自動(dòng)釋放:
int function_A() {
// pd 退出作用域時(shí)虹曙,dog 會(huì)自動(dòng)釋放
std::shared_ptr<dog> pd(new dog());
}
下面我們來(lái)看一下使用 shared_ptr
的一個(gè)陷阱迫横,代碼如下:
class dog;
class Trick;
void train(std::shared_ptr<dog> pd, Trick dogtrick);
Trick getTrick();
int main() {
train(std::shared_ptr<dog> pd(new dog()), getTrick());
}
函數(shù) train
是一個(gè)訓(xùn)練函數(shù),它接受兩個(gè)參數(shù):dog
和 Trick
根吁,即具體訓(xùn)練 dog
的方法由 Trick
提供员淫,但實(shí)際上這行代碼是有問(wèn)題的,問(wèn)題在于击敌,編譯器調(diào)用 new dog()
介返、getTrick()
和 shared_ptr<dog> pd()
這三個(gè)函數(shù)的順序是不確定的,如果編譯器正好按照以下順序來(lái)執(zhí)行:
new dog()
getTrick()
shared_ptr<dog> pd()
同時(shí)在執(zhí)行到第 2 步 getTrick()
時(shí)拋出了異常沃斤,那么 dog
指針就沒(méi)有被智能指針管理起來(lái)圣蝎,于是就發(fā)生了內(nèi)存泄漏。這個(gè)問(wèn)題怎么解決衡瓶,我們把 train
這行代碼拆成兩行就可以了徘公,如下:
int main() {
std::shared_ptr<dog> pd(new dog());
train(pd, getTrick());
}
所以
在初始化
shared_ptr
時(shí),不要和其他語(yǔ)句放在一起使用
RAII 對(duì)象的復(fù)制問(wèn)題
最后哮针,我們?cè)賮?lái)看一個(gè) RAII 對(duì)象復(fù)制的問(wèn)題关面,仍然是上文定義的鎖 Lock
,如果對(duì) Lock
對(duì)象調(diào)用賦值構(gòu)造函數(shù)十厢,即:
Lock L1(&mu);
Lock L2(L1);
此時(shí) m_pm
會(huì)被多個(gè) RAII 對(duì)象持有等太,且因?yàn)槊總€(gè) RAII 對(duì)象析構(gòu)時(shí)都會(huì)對(duì) m_pm
進(jìn)行解鎖,所以程序就無(wú)法控制該鎖的解鎖時(shí)機(jī)了蛮放,因此缩抡,為了解決這問(wèn)題,我們首先想到的方案就是禁止 Lock
對(duì)象的復(fù)制能力包颁,具體做法可以參考之前的文章《沒(méi)有學(xué)不會(huì)的 C++:禁止成員函數(shù)(disallow functions)》瞻想。
今天我們來(lái)學(xué)習(xí)另外一種解決方案,即使用智能指針 shared_ptr
來(lái)解決 RAII 鎖的復(fù)制問(wèn)題娩嚼,思路是這樣的蘑险,因?yàn)橹悄苤羔樦挥性谝糜?jì)數(shù)減為 0 時(shí),才執(zhí)行真正的「清理」工作待锈,如果把「清理」換成解鎖漠其,我們就不用擔(dān)心多次解鎖的問(wèn)題。
正好,shared_ptr
支持用戶自定義「清理」方法和屎,如下是 shared_ptr
的聲明
template<class Other, class D> shared_ptr(Other* ptr, D deleter);
第二個(gè)參數(shù)是引用計(jì)數(shù)為 0 時(shí)調(diào)用的「清理」函數(shù)拴驮,默認(rèn)會(huì)使用 delete
,所以在鎖場(chǎng)景柴信,我們把它替換為 pthread_mutex_unlock
即可套啤,完整的代碼如下:
pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;
class Lock {
private:
std::shared_ptr<pthread_mutex_t> pMutex;
public:
explicit Lock(pthread_mutex_t *pm)
: pMutex(pm, pthread_mutex_unlock) {
pthread_mutex_lock(pm);
}
};
可以看到,Lock
中 pMutex
是一個(gè) pthread_mutex_t
類型的智能指針随常,它在構(gòu)造函數(shù)被調(diào)用時(shí)初始化潜沦,且 deleter
是 pthread_mutex_unlock
,同時(shí)會(huì)調(diào)用 pthread_mutex_lock
進(jìn)行加鎖绪氛,這種機(jī)制不限制 Lock
的復(fù)制唆鸡,且只有在所有「復(fù)制品」都釋放時(shí),才自動(dòng)調(diào)用 pthread_mutex_unlock
進(jìn)行解鎖枣察,這是非常理想的使用 RAII 控制鎖的方法争占。
參考: