一文徹底掌握智能指針
前言:
大家好,今天繼續(xù)分享一篇基礎(chǔ)的智能指針的使用,在分享這篇之前,大家可以看之前分享的兩種智能指針:C++智能指針學(xué)習(xí)(一), 今天我們來(lái)分享剩下的兩個(gè)智能指針:
std::share_ptr
std::weak_ptr
在分享之前,簡(jiǎn)單回憶一下智能指針的概念:
智能指針:它是存儲(chǔ)指向動(dòng)態(tài)分配(堆)對(duì)象指針的類,用于生存期控制,能夠確保在離開指針?biāo)谧饔糜驎r(shí),自動(dòng)正確的銷毀動(dòng)態(tài)內(nèi)存分配的對(duì)象,防止內(nèi)存泄漏。
std::shared_ptr:
std::unique_ptr 對(duì)其持有的資源具有獨(dú)占性,而 std::shared_ptr 持有的資源可以在多個(gè) std::shared_ptr 之間共享,每多一個(gè) std::shared_ptr 對(duì)資源的引用,資源引用計(jì)數(shù)將增加 1,每一個(gè)指向該資源的 std::shared_ptr 對(duì)象析構(gòu)時(shí),資源引用計(jì)數(shù)減 1,最后一個(gè) std::shared_ptr 對(duì)象析構(gòu)時(shí),發(fā)現(xiàn)資源計(jì)數(shù)為 0,將釋放其持有的資源。多個(gè)線程之間,遞增和減少資源的引用計(jì)數(shù)是安全的。(注意:這不意味著多個(gè)線程同時(shí)操作 std::shared_ptr 引用的對(duì)象是安全的)。
std::shared_ptr 提供了一個(gè) use_count() 方法來(lái)獲取當(dāng)前持有資源的引用計(jì)數(shù)。除了上面描述的,std::shared_ptr 用法和 std::unique_ptr 基本相同。
下面是一個(gè)初始化 std::shared_ptr 的示例:
int?main()
{
????//初始化方式1
????std::shared_ptr?sp1(new?int(123));
????//初始化方式2
????std::shared_ptr?sp2;
????sp2.reset(new?int(123));
????//初始化方式3
????std::shared_ptr?sp3;
????sp3?=?std::make_shared(123);
????return?0;
}
和 std::unique_ptr 一樣,你應(yīng)該優(yōu)先使用 std::make_shared 去初始化一個(gè) std::shared_ptr 對(duì)象。
再來(lái)看另外一段代碼:
#include?
#include?
class?A
{
public:
????A()
????{
????????std::cout?<"A?constructor"?<????}
????~A()
????{
????????std::cout?<"A?destructor"?<????}
};
int?main()
{
????{
????????//初始化方式1
????????std::shared_ptr?sp1(new?A());
????????std::cout?<"use?count:?"?<
????????//初始化方式2
????????std::shared_ptr?sp2(sp1);
????????std::cout?<"use?count:?"?<
????????sp2.reset();
????????std::cout?<"use?count:?"?<
????????{
????????????std::shared_ptr?sp3?=?sp1;
????????????std::cout?<"use?count:?"?<????????}
????????std::cout?<"use?count:?"?<????}
????return?0;
}
所以整個(gè)程序的執(zhí)行結(jié)果如下:
A?constructor
use?count:?1
use?count:?2
use?count:?1
use?count:?2
use?count:?1
A?destructor
上述代碼 22 行 sp1 構(gòu)造時(shí),同時(shí)觸發(fā)對(duì)象 A 的構(gòu)造,因此 A 的構(gòu)造函數(shù)會(huì)執(zhí)行。
此時(shí)只有一個(gè) sp1 對(duì)象引用 22 行 new 出來(lái)的 A 對(duì)象(為了敘述方便,下文統(tǒng)一稱之為資源對(duì)象 A),因此代碼 24 行打印出來(lái)的引用計(jì)數(shù)值為 1。
代碼 27 行,利用 sp1 拷貝一份 sp2,導(dǎo)致代碼 28 行打印出來(lái)的引用計(jì)數(shù)為 2。
代碼 30 行調(diào)用 sp2 的 reset() 方法,sp2 釋放對(duì)資源對(duì)象 A 的引用,因此代碼 31 行打印的引用計(jì)數(shù)值再次變?yōu)?1。
代碼 34 行 利用 sp1 再次 創(chuàng)建 sp3,因此代碼 35 行打印的引用計(jì)數(shù)變?yōu)?2。
程序執(zhí)行到 36 行以后,sp3 出了其作用域被析構(gòu),資源 A 的引用計(jì)數(shù)遞減 1,因此 代碼 38 行打印的引用計(jì)數(shù)為 1。
程序執(zhí)行到 39 行以后,sp1 出了其作用域被析構(gòu),在其析構(gòu)時(shí)遞減資源 A 的引用計(jì)數(shù)至 0,并析構(gòu)資源 A 對(duì)象,因此類 A 的析構(gòu)函數(shù)被調(diào)用。
1、std::enable_shared_from_this
實(shí)際開發(fā)中,有時(shí)候需要在類中返回包裹當(dāng)前對(duì)象(this)的一個(gè) std::shared_ptr 對(duì)象給外部使用,C++ 新標(biāo)準(zhǔn)也為我們考慮到了這一點(diǎn),有如此需求的類只要繼承自 std::enable_shared_from_this 模板對(duì)象即可。用法如下:
#include?
#include?
class?A?:?public?std::enable_shared_from_this
{
public:
????A()
????{
????????std::cout?<"A?constructor"?<????}
????~A()
????{
????????std::cout?<"A?destructor"?<????}
????std::shared_ptr?getSelf()
????{
????????return?shared_from_this();
????}
};
int?main()
{
????std::shared_ptr?sp1(new?A());
????std::shared_ptr?sp2?=?sp1->getSelf();
????std::cout?<"use?count:?"?<
????return?0;
}
上述代碼中,類 A 的繼承 std::enable_shared_from_this 并提供一個(gè) getSelf() 方法返回自身的 std::shared_ptr 對(duì)象,在 getSelf() 中調(diào)用 shared_from_this() 即可。
std::enable_shared_from_this 用起來(lái)比較方便,但是也存在很多不易察覺的陷阱。
2、陷阱一:不應(yīng)該共享?xiàng)?duì)象的 this 給智能指針對(duì)象
假設(shè)我們將上面代碼 main 函數(shù) 25 行生成 A 對(duì)象的方式改成一個(gè)棧變量,即:
//其他相同代碼省略...
int?main()
{
????A?a;
????std::shared_ptr?sp2?=?a.getSelf();
????std::cout?<"use?count:?"?<
????return?0;
}
運(yùn)行修改后的代碼會(huì)發(fā)現(xiàn)程序在 std::shared_ptr sp2 = a.getSelf() 產(chǎn)生崩潰。這是因?yàn)?,智能指針管理的是堆?duì)象,棧對(duì)象會(huì)在函數(shù)調(diào)用結(jié)束后自行銷毀,因此不能通過(guò) shared_from_this() 將該對(duì)象交由智能指針對(duì)象管理。
切記:智能指針最初設(shè)計(jì)的目的就是為了管理堆對(duì)象的(即那些不會(huì)自動(dòng)釋放的資源)。
3、陷阱二:避免 std::enable_shared_from_this 的循環(huán)引用問(wèn)題
#include?
#include?
class?A?:?public?std::enable_shared_from_this
{
public:
????A()
????{
????????m_i?=?9;
????????//注意:
????????//比較好的做法是在構(gòu)造函數(shù)里面調(diào)用shared_from_this()給m_SelfPtr賦值
????????//但是很遺憾不能這么做,如果寫在構(gòu)造函數(shù)里面程序會(huì)直接崩潰
????????std::cout?<"A?constructor"?<????}
????~A()
????{
????????m_i?=?0;
????????std::cout?<"A?destructor"?<????}
????void?func()
????{
????????m_SelfPtr?=?shared_from_this();
????}
public:
????int?m_i;
????std::shared_ptr?m_SelfPtr;
};
int?main()
{
????{
????????std::shared_ptr?spa(new?A());
????????spa->func();
????}
????return?0;
}
乍一看上面的代碼好像看不出什么問(wèn)題,讓我們來(lái)實(shí)際運(yùn)行一下看看輸出結(jié)果:
A?constructor
我們發(fā)現(xiàn)在程序的整個(gè)生命周期內(nèi),只有 A 類構(gòu)造函數(shù)的調(diào)用輸出,沒有 A 類析構(gòu)函數(shù)的調(diào)用輸出,這意味著 new 出來(lái)的 A 對(duì)象產(chǎn)生了內(nèi)存泄漏!
我們來(lái)分析一下為什么 new 出來(lái)的 A 對(duì)象得不到釋放。當(dāng)程序執(zhí)行到 39 行后,spa 出了其作用域準(zhǔn)備析構(gòu),在析構(gòu)時(shí)其發(fā)現(xiàn)仍然有另外的一個(gè) std::shared_ptr 對(duì)象即 A::m_SelfPtr 引用了 A,因此 spa 只會(huì)將 A 的引用計(jì)數(shù)遞減為 1,然后就銷毀自身了?,F(xiàn)在留下一個(gè)矛盾的處境:必須銷毀 A 才能銷毀其成員變量 m_SelfPtr,而銷毀 m_SelfPtr 必須先銷毀 A。這就是所謂的 std::enable_shared_from_this 的循環(huán)引用問(wèn)題。我們?cè)趯?shí)際開發(fā)中應(yīng)該避免做出這樣的邏輯設(shè)計(jì),這種情形下即使使用了智能指針也會(huì)造成內(nèi)存泄漏。也就是說(shuō)一個(gè)資源的生命周期可以交給一個(gè)智能指針對(duì)象,但是該智能指針的生命周期不可以再交給整個(gè)資源來(lái)管理。
std::weak_ptr:
std::weak_ptr 是一個(gè)不控制資源生命周期的智能指針,是對(duì)對(duì)象的一種弱引用,只是提供了對(duì)其管理的資源的一個(gè)訪問(wèn)手段,引入它的目的為協(xié)助 std::shared_ptr 工作。
std::weak_ptr 可以從一個(gè) std::shared_ptr 或另一個(gè) std::weak_ptr 對(duì)象構(gòu)造,std::shared_ptr 可以直接賦值給 std::weak_ptr ,也可以通過(guò) std::weak_ptr 的 lock() 函數(shù)來(lái)獲得 std::shared_ptr。它的構(gòu)造和析構(gòu)不會(huì)引起引用計(jì)數(shù)的增加或減少。std::weak_ptr 可用來(lái)解決 std::shared_ptr 相互引用時(shí)的死鎖問(wèn)題(即兩個(gè)std::shared_ptr 相互引用,那么這兩個(gè)指針的引用計(jì)數(shù)永遠(yuǎn)不可能下降為 0, 資源永遠(yuǎn)不會(huì)釋放)。
示例代碼如下:
#include?
#include?
int?main()
{
????//創(chuàng)建一個(gè)std::shared_ptr對(duì)象
????std::shared_ptr?sp1(new?int(123));
????std::cout?<"use?count:?"?<
????//通過(guò)構(gòu)造函數(shù)得到一個(gè)std::weak_ptr對(duì)象
????std::weak_ptr?sp2(sp1);
????std::cout?<"use?count:?"?<
????//通過(guò)賦值運(yùn)算符得到一個(gè)std::weak_ptr對(duì)象
????std::weak_ptr?sp3?=?sp1;
????std::cout?<"use?count:?"?<
????//通過(guò)一個(gè)std::weak_ptr對(duì)象得到另外一個(gè)std::weak_ptr對(duì)象
????std::weak_ptr?sp4?=?sp2;
????std::cout?<"use?count:?"?<
????return?0;
}
運(yùn)行結(jié)果:
use?count:?1
use?count:?1
use?count:?1
use?count:?1
無(wú)論通過(guò)何種方式創(chuàng)建 std::weak_ptr 都不會(huì)增加資源的引用計(jì)數(shù),因此每次輸出引用計(jì)數(shù)的值都是 1。
既然,std::weak_ptr 不管理對(duì)象的生命周期,那么其引用的對(duì)象可能在某個(gè)時(shí)刻被銷毀了,如何得知呢?std::weak_ptr 提供了一個(gè) expired() 方法來(lái)做這一項(xiàng)檢測(cè),返回 true,說(shuō)明其引用的資源已經(jīng)不存在了;返回 false,說(shuō)明該資源仍然存在,這個(gè)時(shí)候可以使用 std::weak_ptr 的 lock() 方法得到一個(gè) std::shared_ptr 對(duì)象然后繼續(xù)操作資源,以下代碼演示了該用法:
//?tmpConn_?是一個(gè)?std::weak_ptr?對(duì)象
//?tmpConn_?引用的TcpConnection已經(jīng)銷毀,直接返回
if?(tmpConn_.expired())
????return;
std::shared_ptr?conn?=?tmpConn_.lock();
if?(conn)
{
????//對(duì)conn進(jìn)行操作,省略...
}
有讀者可能對(duì)上述代碼產(chǎn)生疑問(wèn),既然使用了 std::weak_ptr 的 expired() 方法判斷了對(duì)象是否存在,為什么不直接使用 std::weak_ptr 對(duì)象對(duì)引用資源進(jìn)行操作呢?實(shí)際上這是行不通的,std::weak_ptr 類沒有重寫 operator-> 和 operator* 方法,因此不能像 std::shared_ptr 或 std::unique_ptr 一樣直接操作對(duì)象,同時(shí) std::weak_ptr 類也沒有重寫 operator! 操作,因此也不能通過(guò) std::weak_ptr 對(duì)象直接判斷其引用的資源是否存在:
#include?
class?A
{
public:
????void?doSomething()
????{
????}
};
int?main()
{
????std::shared_ptr?sp1(new?A());
????std::weak_ptr?sp2(sp1);
????//正確代碼
????if?(sp1)
????{
????????//正確代碼
????????sp1->doSomething();
????????(*sp1).doSomething();
????}
????//正確代碼
????if?(!sp1)
????{
????}
????//錯(cuò)誤代碼,無(wú)法編譯通過(guò)
????//if?(sp2)
????//{
????//????//錯(cuò)誤代碼,無(wú)法編譯通過(guò)
????//????sp2->doSomething();
????//????(*sp2).doSomething();
????//}
????//錯(cuò)誤代碼,無(wú)法編譯通過(guò)
????//if?(!sp2)
????//{
????//}
????return?0;
}
之所以 std::weak_ptr 不增加引用資源的引用計(jì)數(shù)不管理資源的生命周期,是因?yàn)?,即使它?shí)現(xiàn)了以上說(shuō)的幾個(gè)方法,調(diào)用它們也是不安全的,因?yàn)樵谡{(diào)用期間,引用的資源可能恰好被銷毀了,這會(huì)造成棘手的錯(cuò)誤和麻煩。
因此,std::weak_ptr 的正確使用場(chǎng)景是那些資源如果可能就使用,如果不可使用則不用的場(chǎng)景,它不參與資源的生命周期管理。例如,網(wǎng)絡(luò)分層結(jié)構(gòu)中,Session 對(duì)象(會(huì)話對(duì)象)利用 Connection 對(duì)象(連接對(duì)象)提供的服務(wù)工作,但是 Session 對(duì)象不管理 Connection 對(duì)象的生命周期,Session 管理 Connection 的生命周期是不合理的,因?yàn)榫W(wǎng)絡(luò)底層出錯(cuò)會(huì)導(dǎo)致 Connection 對(duì)象被銷毀,此時(shí) Session 對(duì)象如果強(qiáng)行持有 Connection 對(duì)象與事實(shí)矛盾。
std::weak_ptr 的應(yīng)用場(chǎng)景,經(jīng)典的例子是訂閱者模式或者觀察者模式中。這里以訂閱者為例來(lái)說(shuō)明,消息發(fā)布器只有在某個(gè)訂閱者存在的情況下才會(huì)向其發(fā)布消息,而不能管理訂閱者的生命周期。
class?Subscriber
{
};
class?SubscribeManager
{
public:
????void?publish()
????{
????????for?(const?auto?&iter?:?m_subscribers)
????????{
????????????if?(!iter.expired())
????????????{
????????????????//TODO:給訂閱者發(fā)送消息
????????????}
????????}
????}
private:
????std::vector>?m_subscribers;
};
智能指針的大小:
一個(gè) std::unique_ptr 對(duì)象大小與裸指針大小相同(即 sizeof(std::unique_ptr) == sizeof(void)),而 std::shared_ptr 的大小是 std::unique_ptr 的一倍。以下是我分別在 Visual Studio 2019 和 gcc/g++ 4.8 上(二者都編譯成 x64 程序)的測(cè)試結(jié)果:
#include?
#include?
#include?
int?main()
{
????std::shared_ptr?sp0;
????std::shared_ptr?sp1;
????sp1.reset(new?std::string());
????std::unique_ptr?sp2;
????std::weak_ptr?sp3;
????std::cout?<"sp0?size:?"?<????std::cout?<"sp1?size:?"?<????std::cout?<"sp2?size:?"?<????std::cout?<"sp3?size:?"?<
????return?0;
}
Visual Studio 2019 (32bit) 運(yùn)行結(jié)果:
sp0?size:8
sp1?size:8
sp2?size:4
sp3?size:8
gcc/g++ (64bit) 運(yùn)行結(jié)果:
sp0?size:16
sp1?size:16
sp2?size:8
sp3?size:16
在 32 位機(jī)器上,std_unique_ptr 占 4 字節(jié),std::shared_ptr 和 std::weak_ptr 占 8 字節(jié)。
在 64 位機(jī)器上,std_unique_ptr 占 8 字節(jié),std::shared_ptr 和 std::weak_ptr 占 16 字節(jié)。
也就是說(shuō),std_unique_ptr 的大小總是和原始指針大小一樣,std::shared_ptr 和 std::weak_ptr 大小是原始指針的一倍。
智能指針使用注意事項(xiàng):
C++ 新標(biāo)準(zhǔn)提倡的理念之一是不應(yīng)該再手動(dòng)調(diào)用 delete 或者 free 函數(shù)去釋放內(nèi)存了,而應(yīng)該把它們交給新標(biāo)準(zhǔn)提供的各種智能指針對(duì)象。C++ 新標(biāo)準(zhǔn)中的各種智能指針是如此的實(shí)用與強(qiáng)大,在現(xiàn)代 C++ 項(xiàng)目開發(fā)中,我們應(yīng)該盡量去使用它們。智能指針雖然好用,但稍不注意,也可能存在許多難以發(fā)現(xiàn)的 bug,這里我根據(jù)經(jīng)驗(yàn)總結(jié)了幾條:
一旦一個(gè)對(duì)象使用智能指針管理后,就不該再使用原始裸指針去操作, 看一段代碼:
#include?
class?Subscriber
{
};
int?main()
{
????Subscriber?*pSubscriber?=?new?Subscriber();
????std::unique_ptr?spSubscriber(pSubscriber);
????delete?pSubscriber;
????return?0;
}
這段代碼利用創(chuàng)建了一個(gè)堆對(duì)象 Subscriber,然后利用智能指針 spSubscriber 去管理之,可以卻私下利用原始指針銷毀了該對(duì)象,這讓智能指針對(duì)象 spSubscriber 情何以堪???
記住,一旦智能指針對(duì)象接管了你的資源,所有對(duì)資源的操作都應(yīng)該通過(guò)智能指針對(duì)象進(jìn)行,不建議再通過(guò)原始指針進(jìn)行操作了。
當(dāng)然,除了 std::weak_ptr 之外,std::unique_ptr 和 std::shared_ptr 都提供了獲取原始指針的方法——get() 函數(shù)。
int?main()
{
????Subscriber?*pSubscriber?=?new?Subscriber();
????std::unique_ptr?spSubscriber(pSubscriber);
????//pTheSameSubscriber和pSubscriber指向同一個(gè)對(duì)象
????Subscriber?*pTheSameSubscriber?=?spSubscriber.get();
????return?0;
}
分清楚場(chǎng)合應(yīng)該使用哪種類型的智能指針:
通常情況下,如果你的資源不需要在其他地方共享,那么應(yīng)該優(yōu)先使用 std::unique_ptr,反之使用 std::shared_ptr,當(dāng)然這是在該智能指針需要管理資源的生命周期的情況下;如果不需要管理對(duì)象的生命周期,請(qǐng)使用 std::weak_ptr。
認(rèn)真考慮,避免操作某個(gè)引用資源已經(jīng)釋放的智能指針
前面的例子,一定讓你覺得非常容易知道一個(gè)智能指針的持有的資源是否還有效,但是還是建議在不同場(chǎng)景謹(jǐn)慎一點(diǎn),有些場(chǎng)景是很容易造成誤判。例如下面的代碼:
#include?
#include?
class?T
{
public:
????void?doSomething()
????{
????????std::cout?<"T?do?something..."?<????}
private:
????int?m_i;
};
int?main()
{
????std::shared_ptr?sp1(new?T());
????const?auto?&sp2?=?sp1;
????sp1.reset();
????//由于sp2已經(jīng)不再持有對(duì)象的引用,程序會(huì)在這里出現(xiàn)意外的行為
????sp2->doSomething();
????return?0;
}
上述代碼中,sp2 是 sp1 的引用,sp1 被置空后,sp2 也一同為空。這時(shí)候調(diào)用 sp2->doSomething(),sp2->(即 operator->)在內(nèi)部會(huì)調(diào)用 get() 方法獲取原始指針對(duì)象,這時(shí)會(huì)得到一個(gè)空指針(地址為 0),繼續(xù)調(diào)用 doSomething() 導(dǎo)致程序崩潰。
你一定仍然覺得這個(gè)例子也能很明顯地看出問(wèn)題,ok,讓我們把這個(gè)例子放到實(shí)際開發(fā)中再來(lái)看一下:
//連接斷開
void?MonitorServer::OnClose(const?std::shared_ptr?&conn)
{
????std::lock_guard?guard(m_sessionMutex);
????for?(auto?iter?=?m_sessions.begin();?iter?!=?m_sessions.end();?++iter)
????{
????????//通過(guò)比對(duì)connection對(duì)象找到對(duì)應(yīng)的session
????????if?((*iter)->GetConnectionPtr()?==?conn)
????????{
????????????m_sessions.erase(iter);
????????????//注意這里:程序在此處崩潰
????????????LOGI("monitor?client?disconnected:?%s",?conn->peerAddress().toIpPort().c_str());
????????????break;
????????}
????}
}
該段程序會(huì)在代碼 12 行處崩潰,崩潰原因是調(diào)用了 conn->peerAddress() 方法。為什么這個(gè)方法的調(diào)用可能會(huì)引起崩潰?現(xiàn)在可以一目了然地看出了嗎?
崩潰原因是傳入的 conn 對(duì)象和上一個(gè)例子中的 sp2 一樣都是另外一個(gè) std::shared_ptr 的引用,當(dāng)連接斷開時(shí),對(duì)應(yīng)的 TcpConnection 對(duì)象可能早已被銷毀,而 conn 引用就會(huì)變成空指針(嚴(yán)格來(lái)說(shuō)是不再擁有一個(gè) TcpConnection 對(duì)象),此時(shí)調(diào)用 TcpConnection 的 peerAddress() 方法就會(huì)產(chǎn)生和上一個(gè)示例一樣的錯(cuò)誤。
作為類成員變量時(shí),應(yīng)該優(yōu)先使用前置聲明(forward declarations)
我們知道,為了減小編譯依賴加快編譯速度和生成二進(jìn)制文件的大小,C/C++ 項(xiàng)目中一般在 *.h 文件對(duì)于指針類型盡量使用前置聲明,而不是直接包含對(duì)應(yīng)類的頭文件。例如:
//Test.h
//在這里使用A的前置聲明,而不是直接包含A.h文件
class?A;
class?Test
{
public:
????Test();
????~Test();
private:
????A?*m_pA;
};
同樣的道理,在頭文件中當(dāng)使用智能指針對(duì)象作為類成員變量時(shí),也應(yīng)該優(yōu)先使用前置聲明去引用智能指針對(duì)象的包裹類,而不是直接包含包含類的頭文件。
//Test.h
#include?
//智能指針包裹類A,這里優(yōu)先使用A的前置聲明,而不是直接包含A.h
class?A;
class?Test
{
public:
????Test();
????~Test();
private:
????std::unique_ptr?m_spA;
};
Modern C/C++ 已經(jīng)變?yōu)?C/C++ 開發(fā)的趨勢(shì),希望能善用和熟練這些智能指針對(duì)象。
智能指針的簡(jiǎn)單實(shí)現(xiàn):
最后,給出智能指針的簡(jiǎn)單實(shí)現(xiàn),因?yàn)?weak_ptr 作為弱引用指針,其實(shí)現(xiàn)依賴于 Counter 計(jì)數(shù)器類和 shared_ptr 的賦值,所以先進(jìn)行 Counter 計(jì)數(shù)器類和 share_ptr 的簡(jiǎn)單實(shí)現(xiàn)。
Counter的簡(jiǎn)單實(shí)現(xiàn):
/*
?*?計(jì)數(shù)器
?*?Counter對(duì)象就是用來(lái)申請(qǐng)一塊內(nèi)存存儲(chǔ)引用計(jì)數(shù)
?*?m_refCount是SharedPtr的引用計(jì)數(shù)
?*?m_weakCount是WeakPtr的引用計(jì)數(shù)
?*?當(dāng)m_weakCount為0時(shí)刪除Counter對(duì)象
?*/
template?
class?Counter
{
????friend?class?SharedPtr;
????friend?class?WeakPtr;
public:
????Counter()?:?m_refCount(0),?m_weakCount(0)?{}
????virtual?~Counter()?{}
private:
????Counter(const?Counter?&)?=?delete;
????Counter?&operator=(const?Counter?&)?=?delete;
private:
????atomic_uint?m_refCount;??//?#shared,原子操作
????atomic_uint?m_weakCount;?//?#weak,原子操作
};
shared_ptr的簡(jiǎn)單實(shí)現(xiàn):
/*
?*?SharedPtr的簡(jiǎn)單實(shí)現(xiàn)
?*/
template?
class?SharedPtr
{
????friend?class?WeakPtr;
public:
????/*
?????*?構(gòu)造函數(shù),用原生指針構(gòu)造
?????*/
????SharedPtr(T?*ptr)?:?m_ptr(ptr),?m_cnt(new?Counter)
????{
????????if?(ptr)
????????{
????????????m_cnt->m_refCount?=?1;
????????}
????????cout?<"Ptr?Construct?S."?<????}
????~SharedPtr()
????{
????????release();
????}
????/*
?????*?拷貝構(gòu)造函數(shù),用另一個(gè)SharedPtr對(duì)象構(gòu)造
?????*/
????SharedPtr(const?SharedPtr?&s)
????{
????????m_ptr?=?s.m_ptr;
????????s.m_cnt->m_refCount++;
????????m_cnt?=?s.m_cnt;
????????cout?<"S?Copy?Construct?S."?<????}
????/*
?????*?拷貝構(gòu)造函數(shù),用另一個(gè)WeakPtr對(duì)象構(gòu)造
?????*?為了WeakPtr對(duì)象調(diào)用自己的lock()方法將自己傳進(jìn)來(lái)構(gòu)造一個(gè)SharedPtr返回
?????*/
????SharedPtr(const?WeakPtr?&w)
????{
????????m_ptr?=?w.m_ptr;
????????w.m_cnt->m_refCount++;
????????m_cnt?=?w.m_cnt;
????????cout?<"W?Copy?Construct?S."?<????}
????/*
?????*?賦值構(gòu)造函數(shù),用另一個(gè)SharedPtr對(duì)象構(gòu)造
?????*/
????SharedPtr?&operator=(const?SharedPtr?&s)
????{
????????if?(this?!=?s)
????????{
????????????this->release();
????????????m_ptr?=?s.m_ptr;
????????????s.m_cnt->m_refCount++;
????????????m_cnt?=?s.m_cnt;
????????????cout?<"S?Assign?Construct?S."?<????????}
????????return?*this;
????}
????T?&operator*()
????{
????????return?*m_ptr;
????}
????T?*operator->()
????{
????????return?m_ptr;
????}
protected:
????void?release()
????{
????????m_cnt->m_refCount--;
????????if?(m_cnt->m_refCount?1)
????????{
????????????delete?m_ptr;
????????????m_ptr?=?nullptr;
????????????cout?<"SharedPtr?Delete?Ptr."?<????????????if?(m_cnt->m_weakCount?1)
????????????{
????????????????delete?m_cnt;
????????????????m_cnt?=?nullptr;
????????????????cout?<"SharedPtr?Delete?Cnt."?<????????????}
????????????cout?<"SharedPtr?Release."?<????????}
????}
private:
????T?*m_ptr;
????Counter?*m_cnt;
};
weak_ptr的簡(jiǎn)單實(shí)現(xiàn):
template?
class?WeakPtr
{
public:
????/*
?????*?構(gòu)造函數(shù),用SharedPtr對(duì)象構(gòu)造
?????*/
????WeakPtr(SharedPtr?&s)?:?m_ptr(s.m_ptr),?m_cnt(s.m_cnt)
????{
????????m_cnt->m_weakCount++;
????????cout?<"S?Construct?W."?<????}
????/*
?????*?構(gòu)造函數(shù),用WeakPtr對(duì)象構(gòu)造
?????*/
????WeakPtr(WeakPtr?&w)?:?m_ptr(w.m_ptr),?m_cnt(w.m_cnt)
????{
????????m_cnt->m_weakCount++;
????????cout?<"W?Construct?W."?<????}
????~WeakPtr()
????{
????????release();
????}
????/*
?????*?賦值構(gòu)造函數(shù),用另一個(gè)SharedPtr對(duì)象構(gòu)造
?????*/
????WeakPtr?&operator=(SharedPtr?&s)
????{
????????release();
????????m_cnt?=?s.m_cnt;
????????m_cnt->m_weakCount++;
????????m_ptr?=?s.m_ptr;
????????cout?<"S?Assign?Construct?W."?<????????return?*this;
????}
????/*
?????*?賦值構(gòu)造函數(shù),用另一個(gè)WeakPtr對(duì)象構(gòu)造
?????*/
????WeakPtr?&operator=(WeakPtr?&w)
????{
????????if?(this?!=?&w)
????????{
????????????release();
????????????m_cnt?=?w.m_cnt;
????????????m_cnt->m_weakCount++;
????????????m_ptr?=?w->m_ptr;
????????????cout?<"W?Assign?Construct?W."?<????????}
????????return?*this;
????}
????/*
?????*?WeakPtr通過(guò)lock函數(shù)獲得SharedPtr
?????*/
????SharedPtr?&lock()
????{
????????return?SharedPtr(*this);
????}
????/*
?????*?檢查SharedPtr是否已過(guò)期
?????*/
????bool?expired()
????{
????????if?(m_cnt)
????????{
????????????if?(m_cnt->m_refCount?>?0)
????????????????return?false;
????????}
????????return?true;
????}
private:
????WeakPtr()?=?delete;???????WeakPtr禁止默認(rèn)構(gòu)造,只能從SharedPtr或者WeakPtr構(gòu)造
????T?&operator*()?=?delete;??//WeakPtr禁止*
????T?*operator->()?=?delete;?//WeakPtr禁止->
private:
????void?release()
????{
????????if?(m_cnt)
????????{
????????????m_cnt->m_weakCount--;
????????????if?(m_cnt->m_weakCount?1?&&?m_cnt->m_refCount?1)
????????????{
????????????????delete?m_cnt;
????????????????m_cnt?=?nullptr;
????????????????cout?<"Delete?Cnt."?<????????????}
????????????cout?<"WeakPtr?Release."?<????????}
????}
private:
????T?*m_ptr;
????Counter?*m_cnt;
};
上面的實(shí)現(xiàn)可能不是非常嚴(yán)謹(jǐn),僅實(shí)現(xiàn)了常用的的函數(shù)接口而已,但其主要的目的是為了更深刻的了解智能指針的原理,這樣才能更有把握的使用智能指針,只有了解它的內(nèi)部實(shí)現(xiàn),對(duì)于使用中的一些坑才能有效避免。
文章相關(guān)參考:https://blog.csdn.net/code_peak/article/details/119722167
文章整理:txp玩Linux
推薦:
面試常問(wèn)的 C/C++ 問(wèn)題,你能答上來(lái)幾個(gè)?
很多人搞不清 C++ 中的 delete 和 delete[ ] 的區(qū)別
看懂別人的代碼,總得懂點(diǎn) C++ lambda 表達(dá)式吧
Java、C++ 內(nèi)存模型都不知道,還敢說(shuō)自己是高級(jí)工程師?
C++ std::thread 必須要熟悉的幾個(gè)知識(shí)點(diǎn)
