沒(méi)有學(xué)不會(huì)的C++:RAII 技術(shù)(Resource Aquisition is Initialization)

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ù):dogTrick根吁,即具體訓(xùn)練 dog 的方法由 Trick 提供员淫,但實(shí)際上這行代碼是有問(wèn)題的,問(wèn)題在于击敌,編譯器調(diào)用 new dog()介返、getTrick()shared_ptr<dog> pd() 這三個(gè)函數(shù)的順序是不確定的,如果編譯器正好按照以下順序來(lái)執(zhí)行:

  1. new dog()
  2. getTrick()
  3. 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);
            }
};

可以看到,LockpMutex 是一個(gè) pthread_mutex_t 類型的智能指針随常,它在構(gòu)造函數(shù)被調(diào)用時(shí)初始化潜沦,且 deleterpthread_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 控制鎖的方法争占。

參考:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市序目,隨后出現(xiàn)的幾起案子臂痕,更是在濱河造成了極大的恐慌,老刑警劉巖猿涨,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件握童,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡叛赚,警方通過(guò)查閱死者的電腦和手機(jī)澡绩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)俺附,“玉大人英古,你說(shuō)我怎么就攤上這事£级粒” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵膨桥,是天一觀的道長(zhǎng)蛮浑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)只嚣,這世上最難降的妖魔是什么沮稚? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮册舞,結(jié)果婚禮上蕴掏,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好盛杰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布挽荡。 她就那樣靜靜地躺著,像睡著了一般即供。 火紅的嫁衣襯著肌膚如雪定拟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天逗嫡,我揣著相機(jī)與錄音青自,去河邊找鬼。 笑死驱证,一個(gè)胖子當(dāng)著我的面吹牛延窜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抹锄,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼逆瑞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了祈远?” 一聲冷哼從身側(cè)響起呆万,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎车份,沒(méi)想到半個(gè)月后谋减,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扫沼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年出爹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缎除。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡严就,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出器罐,到底是詐尸還是另有隱情梢为,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布轰坊,位于F島的核電站铸董,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏肴沫。R本人自食惡果不足惜粟害,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望颤芬。 院中可真熱鬧悲幅,春花似錦套鹅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至郁副,卻和暖如春减牺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背存谎。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工拔疚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人既荚。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓稚失,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親恰聘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子句各,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容