智能指針之使用

上一章:智能指針 (3)

目錄

有了智能指針的定義,我們現(xiàn)在來(lái)講講智能指針如何使用優(yōu)勢(shì)以及一些問(wèn)題。
1,unique_ptr

具有擁有語(yǔ)義的類(lèi)成員變量
傳統(tǒng)情況下,具有擁有語(yǔ)義類(lèi)成員變量可使用:普通成員,普通指針。

普通成員變量, 需要在頭文件里面包含所擁有成員的頭文件,這會(huì)增加編譯的復(fù)雜度。

//Test1.h
#include "Test1.h"
class Test1
{
    Type1 ownedByTest1;
public:
    Test1();
    ~Test1();
};

//Test1.cpp
#include "Test.h"
#include "Type1.h"
Test1::Test1()
{}
    
Test1:: ~Test1()
{}

普通指針, 只需要前置聲明,在cpp文件里面包含成員的頭文件即可,這樣不會(huì)增加編譯的復(fù)雜度,只需要增加鏈接即可,但是這種裸指針,必須在函數(shù)析構(gòu)時(shí)顯示的刪除,在項(xiàng)目龐大,結(jié)構(gòu)復(fù)雜的情況下,程序員就有可能犯錯(cuò)忘記刪除資源,造成資源泄露。

//Test2.h
class Type2;
class Test2
{
    Type2* ownedByTest2;
public:
    Test2();
    ~Test2();
};

//Test2.cpp
#include "Test.h"
#include "Type2.h"
Test2::Test2()
    : ownedByTest2(new Type2)
{}
    
Test2:: ~Test2()
{
    delete ownedByTest2;
}

unique_ptr綜合了普通成員變量和指針成員變量的優(yōu)點(diǎn),又沒(méi)有他們的缺點(diǎn)。
(1) unique_ptr較普通成員,主要是通過(guò)前置聲明的方式減少頭文件包含。
(2) unique_ptr普通指針,可以去掉析構(gòu)時(shí)顯式delete成員。

// Test3.h
#include <memory>
class Type3;
class Test3
{
    std::unique_ptr<Type3> ownedByTest3;
};

// Test3.cpp
Test3::Test3()
     : ownedByTest3(new Type3)
{}

Test3::~Test3()
{}

函數(shù)參數(shù)傳遞
其意義是通過(guò)move語(yǔ)意,把內(nèi)存的所有權(quán)轉(zhuǎn)移。
對(duì)于C++11之前,auto_ptr也有這個(gè)功能,只是auto_ptr語(yǔ)義沒(méi)有那么明確。造成很多沒(méi)有理解正確的人濫用auto_ptr,造成程序不可預(yù)期的結(jié)果。
C++11之后,auto_ptr被deprecated了,取而代之的是unique_ptr,而對(duì)于unique_ptr,其語(yǔ)義非常明確,必須要通過(guò)std::move 進(jìn)程所有權(quán)轉(zhuǎn)移。

void doSomething(std::unique_ptr<Type> type)
{
    // do something
}
void moveOnwerShip(std::unique_ptr<Type> type)
{
    doSomething(type); // 錯(cuò)誤, 編譯無(wú)法通過(guò)
    doSomething(std::move(type)); // 正確, OwnerShip轉(zhuǎn)移
}

2, shared_ptr

具有關(guān)聯(lián)屬性的類(lèi)成員變量,即有多個(gè)對(duì)象都關(guān)聯(lián)此成員。
具有關(guān)聯(lián)屬性的類(lèi)成員變量可使用:引用, 普通指針。

class Test1
{
    Type1& type1_;
public:
    Test1(Type1& type1);
};

Test1::Test1(Type1& type1)
    :type1_(type1)
{}

class Test2
{
    Type2* type2_;
public:
    Test2(Type1* type2);
};

Test2::Test2(Type2* type2)
    :type2_(type2)
{}

使用引用和指針,要非常注意的是在類(lèi)對(duì)象的析構(gòu)順序,如果用這些引用或指針的對(duì)象被析構(gòu)掉后,那么這些指針或引用就無(wú)效了,如果此時(shí)被引用關(guān)聯(lián)的對(duì)象再去使用,就會(huì)產(chǎn)生資源錯(cuò)誤(比如段錯(cuò)誤)。

要解決類(lèi)對(duì)象析構(gòu)順序問(wèn)題,就必須用到一種技術(shù)——引用計(jì)數(shù)(比如Com指針),而C++11之后,標(biāo)準(zhǔn)庫(kù)里面的shared_ptr為我們提供了這一解決方案。
(1) shared_ptr較引用,可以不用考慮引用被關(guān)聯(lián)對(duì)象析構(gòu)的順序。也就是說(shuō),對(duì)于引用被關(guān)聯(lián)對(duì)象,必須在引用對(duì)象析構(gòu)前析構(gòu)。否則引用對(duì)象一旦析構(gòu),醫(yī)用就即可失效,引用使用就會(huì)引起內(nèi)存錯(cuò)誤。而shared_ptr只有在引用計(jì)數(shù)為0(即最后一份引用被銷(xiāo)毀)時(shí),才會(huì)去真正銷(xiāo)毀資源。

// 只有在其他地方的share_ptr 都被銷(xiāo)毀了,在Test析構(gòu)的時(shí)候才會(huì)去銷(xiāo)毀Type資源。
class Test
{
    std::shared_ptr<Type> type_;
public:
    Test(std::shared_ptr<Type> type);
};

Test::Test(std::shared_ptr<Type> type)
    :type_(type)
{}

(2) shared_ptr較普通指針,普通指針存在和引用同樣的問(wèn)題,因此shared_ptr有著同樣的優(yōu)勢(shì)。

函數(shù)參數(shù)傳遞
其傳遞具有引用或指針傳遞的優(yōu)勢(shì),并且無(wú)需擔(dān)心對(duì)象是否有效,因?yàn)閟hared_ptr所指向的對(duì)象一直都是有效的(只要初始化時(shí)是有效的),share_ptr每次賦值,只是引用計(jì)數(shù)+1,銷(xiāo)毀時(shí),只是引用計(jì)數(shù)-1,因此參數(shù)傳遞時(shí),沒(méi)有太多的額外開(kāi)銷(xiāo)。

3, 作用域問(wèn)題

如果在某個(gè)作用域里面,有多個(gè)分支要手動(dòng)銷(xiāo)毀對(duì)象,那么智能指針便是最好的選擇,不會(huì)因?yàn)槁?xiě)而產(chǎn)生內(nèi)存泄漏。

比如如下代碼,非常容易出錯(cuò),若以后case還要增加,則情況會(huì)變得更加復(fù)雜。

void mutiStatesReturn(const State& state)
{
    Type* type = new Type();
    switch(state)
    {
    case STATE1:
        handle1();
        delete type;
        return;
    case STATE2:
        handle2(type);
        break;
    default:
        break;
    }
    handle3(type);
    delete type;
}

如果用share_ptr, 則情況就會(huì)變得簡(jiǎn)單多,無(wú)需考慮type的資源釋放問(wèn)題:

void mutiStatesReturn(const State& state)
{
    std::shared_ptr<Type> type(new Type());
    switch(state)
    {
    case STATE1:
        handle1();
        return;
    case STATE2:
        handle2(type);
        break;
    default:
        break;
    }
    handle3(type);
}

4, shared_ptr存在的問(wèn)題
(1)首先就是環(huán)形引用問(wèn)題,即A1引用A2,A2引用A3 ..... Ax引用A1,這個(gè)問(wèn)題不在累贅,解決方案是用weak_ptr.
(2)正所謂成也蕭何,敗也蕭何,shared_ptr為了解決對(duì)象銷(xiāo)毀的順序問(wèn)題,但是也正是它可以解決這個(gè)問(wèn)題,導(dǎo)致它被濫用。

比如有三個(gè)對(duì)象同時(shí)引用了一個(gè)shared_ptr, 那么通過(guò)代碼走讀,我們很容易就能知道他們之間的關(guān)系,誰(shuí)先創(chuàng)立,誰(shuí)后銷(xiāo)毀。

但是試想一下,如果有30個(gè)對(duì)象同時(shí)引用一個(gè)shared_ptr,那么,很難搞清楚個(gè)中關(guān)系,這個(gè)對(duì)維護(hù)的人來(lái)說(shuō),簡(jiǎn)直就是shit。

在無(wú)shared_ptr時(shí)代,一般建立對(duì)象有著嚴(yán)格的順序,如下方式

啟動(dòng)模塊1
@啟動(dòng)子模塊11
@@創(chuàng)建對(duì)象111
@@創(chuàng)建對(duì)象112
......
@啟動(dòng)子模塊12
......
啟動(dòng)模塊2
@啟動(dòng)子模塊21
.......
@啟動(dòng)子模塊22
......

層次非常分明,因?yàn)槿绻S意創(chuàng)建,并且有多對(duì)象引用同一資源,那么必然會(huì)造成資源多次釋放。

而有了智能指針之后,很多人就不遵循層次化的原則, 寫(xiě)的人輕松,看的人頭大罵娘。

因此個(gè)人觀點(diǎn)是,即使有引用計(jì)數(shù)的智能指針,還是要有明確的先后次序,能用引用代替shared_ptr,則應(yīng)該代替,shared_ptr到處存, 單例到處創(chuàng)建要不得(單例這個(gè)問(wèn)題,以后有機(jī)會(huì)專(zhuān)門(mén)講講, 它最大的缺點(diǎn)就是初始化和銷(xiāo)毀順序不明確)。

下一章: 容器 - vector (1)
目錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容