上一章:智能指針 (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)毀順序不明確)。