柚子快報(bào)激活碼778899分享:開發(fā)語言 【C++】share
柚子快報(bào)激活碼778899分享:開發(fā)語言 【C++】share
一、share_ptr 的簡單使用
1.1、基本用法
從較淺的層面看,智能指針是利用了一種叫做RAII(資源獲取即初始化)的技術(shù)對普通的指針進(jìn)行封裝,這使得智能指針實(shí)質(zhì)是一個(gè)對象,行為表現(xiàn)的卻像一個(gè)指針。
智能指針的作用是防止忘記調(diào)用delete釋放內(nèi)存和程序異常的進(jìn)入catch塊忘記釋放內(nèi)存。另外指針的釋放時(shí)機(jī)也是非常有考究的,多次釋放同一個(gè)指針會(huì)造成程序崩潰,這些都可以通過智能指針來解決。
智能指針的行為類似于一個(gè)常規(guī)指針,與常規(guī)指針之間重要的區(qū)別就是它負(fù)責(zé)自動(dòng)釋放所管理的資源,share_ptr 使用引用計(jì)數(shù),允許多個(gè) share_ptr 指向同一資源,每多一個(gè) share_ptr 指向該資源,share_ptr 的引用計(jì)數(shù)就 +1 ,減為0時(shí)表示沒有 share_ptr 對該資源進(jìn)行引用了,就會(huì)釋放所指向的資源。share_ptr 內(nèi)部中的引用計(jì)數(shù)是線程安全的,但是引用的資源不是線程安全的。
1.2、初始化?
裸指針直接初始化,但不能通過隱式轉(zhuǎn)換來構(gòu)造,因?yàn)?share_ptr 構(gòu)造函數(shù)被聲明為 explicit允許移動(dòng)構(gòu)造與拷貝構(gòu)造通過 make_share 構(gòu)造
#include
#include
class test {};
int main()
{
std::shared_ptr
//std::shared_ptr
std::shared_ptr
std::shared_ptr
f2 = f; // copy賦值運(yùn)算符重載
std::cout << f3.use_count() << " " << f3.unique() << std::endl;
std::shared_ptr
//std::shared_ptr
std::shared_ptr
std::shared_ptr
std::cout << f7.use_count() << " " << f7.unique() << std::endl;
std::shared_ptr
std::shared_ptr
auto f10 = std::make_shared
return 0;
}
在初始化上 share_ptr 與 unique_ptr 在初始化上的方式就有區(qū)別,二者都不支持隱式初始化,但是 unque_ptr 不支持拷貝構(gòu)造和拷貝賦值,而 share_ptr 則都支持。
1.3、刪除器
刪除器可以是普通函數(shù)、函數(shù)對象和 lambda 表達(dá)式等,默認(rèn)的刪除器為 std::default_delete ,內(nèi)部是使用 delete 來實(shí)現(xiàn)的,與 unique_ptr 不同,刪除器不是 share_ptr 類型的組成部分,也就是說兩個(gè) sptr1,sptr2 有不同的刪除器,但只要它們的類型是相同的都可以被放入同一容器中。此外,在動(dòng)態(tài)管理數(shù)組的時(shí)候,share_ptr 需要指定刪除器。智能指針的大小與被引用的資源的大小是無關(guān)的,因?yàn)橹悄苤羔樖且彩峭ㄟ^指針來對該資源進(jìn)行訪問,而不是存儲(chǔ)在智能指針內(nèi)部。
#include
#include
#include
class Frame {};
int main()
{
auto del1 = [](Frame* f){
std::cout << "delete1" << std::endl;
delete f;
};
auto del2 = [](Frame* f){
std::cout << "delete2" << std::endl;
delete f;
};
std::shared_ptr f1(new Frame(), del1);
std::shared_ptr f2(new Frame(), del2);
std::unique_ptr f3(new Frame(), del);
std::vector
v.push_back(f1);
v.push_back(f2);
return 0;
}
二、剖析 share_ptr
2.1、share_ptr 的內(nèi)存模型
share_ptr 的內(nèi)存模型長這樣(直接使用的陳碩大神的圖)
將其簡化只剩下引用計(jì)數(shù)與原始指針后長這樣:
從圖中我們可以看出,share_ptr 包含了一個(gè)指向?qū)ο蟮闹羔樅鸵粋€(gè)指向控制塊的指針。每一個(gè)由share_ptr 管理的對象都有一個(gè)控制塊,除了包含強(qiáng)引用計(jì)數(shù)、弱引用技術(shù)之外,還包含了自定義刪除器的副本和分配器的副本以及其他附加數(shù)據(jù)。
2.2、控制塊的創(chuàng)建規(guī)則
std::make_shared 總是創(chuàng)建一個(gè)控制塊從具備所有權(quán)的指針觸發(fā)構(gòu)造一個(gè) share_ptr 的時(shí)候,會(huì)創(chuàng)建一個(gè)控制塊(比如 unique_ptr 在轉(zhuǎn)換成 share_ptr 的時(shí)候會(huì)創(chuàng)建控制塊,因?yàn)?unique_ptr 本身不使用控制塊,同時(shí) unique_ptr 置空)當(dāng) share_ptr 構(gòu)造函數(shù)使用裸指針作為實(shí)參時(shí),會(huì)創(chuàng)建一個(gè)控制塊(也就是說,從同一個(gè)裸指針出發(fā)構(gòu)造多個(gè) share_ptr 的時(shí)候會(huì)創(chuàng)建多個(gè)控制塊,這也就意味著裸指針可能會(huì)被釋放多次。如果想從一個(gè)已經(jīng)擁有控制塊的對象出發(fā)創(chuàng)建一個(gè) share_ptr ,此時(shí)我們就可以傳遞一個(gè) share_ptr 或 weak_ptr 而非裸指針作為構(gòu)造函數(shù)的參數(shù),這樣就不會(huì)創(chuàng)建新的控制器)
因此,盡可能避免將裸指針傳遞給 share_ptr 的一個(gè)有效的辦法是 make_shared。如果必須將一個(gè)裸指針作為參數(shù)傳入到 share_ptr 的構(gòu)造函數(shù),就直接傳遞 new 運(yùn)算符運(yùn)算的結(jié)果而非傳遞一個(gè)裸指針。
2.3、盡量使用 make 函數(shù)
make_shared 內(nèi)部是通過調(diào)用 allocate_shared 來進(jìn)行實(shí)現(xiàn)的,與 new 相比,make 系列函數(shù)的優(yōu)勢:避免代碼冗余,創(chuàng)建智能指針的時(shí)候,被創(chuàng)建的對象只需要寫一次,如 make_shared,而用 new 來進(jìn)行創(chuàng)建智能指針的時(shí)候需要寫兩次。異常安全性,make 系列函數(shù)可編寫異常安全代碼,增強(qiáng)了安全性
使用 make 函數(shù)與使用 new 進(jìn)行構(gòu)造的 share_ptr 內(nèi)存布局如下:
make 系列函數(shù)的局限性:
所有的 make 系列函數(shù)都不允許自定義刪除器make 系列函數(shù)創(chuàng)建對象時(shí),不能接受{}初始化列表(這是因?yàn)橥昝擂D(zhuǎn)發(fā)的轉(zhuǎn)發(fā)函數(shù)是一個(gè)模板函數(shù),利用模板類型進(jìn)行推導(dǎo)。因此無法把{}推導(dǎo)為 initializer_list)也就是說,make 系列只能將圓括號(hào)內(nèi)的參數(shù)進(jìn)行轉(zhuǎn)發(fā)自定義內(nèi)存管理的類(如重載了 operator new 和 operator delete)不建議使用 make_shared 來創(chuàng)建,因?yàn)橹剌d?operator new 和 operator delete 時(shí),往往用來分配和釋放該類精確尺寸的內(nèi)存塊,而 make_shared 創(chuàng)建的 shared_ptr,是一個(gè)自定義了分配器 (allocate_shared) 和刪除器的智能指針,由 allocate_shared 分配的內(nèi)存大小也不等于上述的尺寸,而是在此基礎(chǔ)上加上控制塊的大小對象的內(nèi)存可能無法及時(shí)回收。因?yàn)椋簃ake_shared 只分配一次內(nèi)存,減少了內(nèi)存分配的開銷,使得控制塊和托管對象在同一內(nèi)存塊上分配。而控制塊是由 shared_ptr 和 weak_ptr 共享的,因此兩者共同管理著這個(gè)內(nèi)存塊(托管對象 + 控制塊)。當(dāng)強(qiáng)引用計(jì)數(shù)為 0 時(shí),托管對象被析構(gòu)(即析構(gòu)函數(shù)被調(diào)用),但內(nèi)存塊并未被回收,只有等到最后一個(gè) weak_ptr 離開作用域時(shí),弱引用也減為 0 才會(huì)釋放這塊內(nèi)存塊。原本強(qiáng)引用減為 0 時(shí)就可以釋放的內(nèi)存, 現(xiàn)在變?yōu)榱藦?qiáng)引用和弱引用都減為 0 時(shí)才能釋放, 意外的延遲了內(nèi)存釋放的時(shí)間。這對于內(nèi)存要求高的場景來說,是一個(gè)需要注意的問題。
2.4、引用計(jì)數(shù)
shared_ptr 中的引用計(jì)數(shù)直接關(guān)系到何時(shí)是否進(jìn)行對象的析構(gòu),因此它的變動(dòng)變得尤為重要shared_ptr 的構(gòu)造函數(shù)會(huì)使該引用計(jì)數(shù)遞增,而析構(gòu)函數(shù)則會(huì)使得引用計(jì)數(shù)遞減。但移動(dòng)構(gòu)造表示從一個(gè)已有的 shared_ptr 移動(dòng)構(gòu)造到另一個(gè)新的 shared_ptr ,這意味著一旦新的 shared_ptr 產(chǎn)生后,原有的 shared_ptr 就會(huì)被置空,結(jié)果就是引用計(jì)數(shù)沒有變化??截愘x值構(gòu)造同時(shí)執(zhí)行兩種操作如(例如 p1 和 p2 是指向不同對象的 shared_ptr ,則執(zhí)行? ? ? ? ?p1 = p1 時(shí),將修改 p1 使得其指向 p2 所值的對象,而最初 p1 所指向的對象的引用計(jì)數(shù)遞減,同時(shí) p2 所指向的對象引用計(jì)數(shù)遞增 )reset 函數(shù),如果不帶參數(shù)時(shí),則引用計(jì)數(shù)減 1。如果帶參數(shù)時(shí),如 sp.reset(p) 則 sp 原來指向的對象引用計(jì)數(shù)減 1 ,同時(shí) sp 指向新的對象 p。如果實(shí)施一次遞減后,最后的引用計(jì)數(shù)變?yōu)?0 ,即不再有 shared_ptr 指向該對象,則會(huì)被 shared_ptr 析構(gòu)掉。
需要注意的是:引用計(jì)數(shù)本身是安全且無鎖的,但對象的讀寫則不是。
2.5、this 返回 shared_ptr?
不要將 this 指針返回給 shared_ptr。當(dāng)希望將 this 指針托管給 shared_ptr 時(shí),類需要繼承自? ? ? ? ? std::enable_shared_from_this ,并且從 shared_from_this() 中獲得 shared_ptr 指針。
#include
#include
class Frame {
public:
std::shared_ptr GetThis() {
return std::shared_ptr(this);
}
};
int main()
{
std::shared_ptr f1(new Frame());
std::shared_ptr f2 = f1->GetThis();
std::cout << f1.use_count() << " " << f2.use_count() << std::endl;
std::shared_ptr f3(new Frame());
std::shared_ptr f4 = f3;
std::cout << f3.use_count() << " " << f4.use_count() << std::endl;
return 0;
}
運(yùn)行結(jié)果:
此時(shí)就很奇怪,GetThis() 返回的不是 f1 本身嘛?而為什么 f2 和 f4 的結(jié)果就有這樣的區(qū)別呢?
其實(shí),直接從 this 指針創(chuàng)建,會(huì)為 this 對象創(chuàng)建新的控制塊,也就相當(dāng)于從裸指針重新創(chuàng)建一個(gè)新的控制塊。
為了解決這個(gè)問題,標(biāo)準(zhǔn)庫提供了解決方法:讓類派生于一個(gè)模板類:enable_shared_from_this
通過調(diào)用 shared_from_this 成員函數(shù)獲得一個(gè)和this指針指向相同對象的shared_ptr。
class Frame : public std::enable_shared_from_this {
public:
std::shared_ptr GetThis() {
return shared_from_this();
}
};
原理:
template
class enable_shared_from_this
{
protected:
constexpr enable_shared_from_this() noexcept { }
enable_shared_from_this(const enable_shared_from_this&) noexcept { }
enable_shared_from_this& operator=(const enable_shared_from_this&) noexcept { return *this; }
~enable_shared_from_this() { }
public:
shared_ptr<_Tp> shared_from_this()
{ return shared_ptr<_Tp>(this->_M_weak_this); }
shared_ptr
{ return shared_ptr
private:
template
void _M_weak_assign(_Tp1* __p, const __shared_count<>& __n) const noexcept
{ _M_weak_this._M_assign(__p, __n); }
template
friend void __enable_shared_from_this_helper(const __shared_count<>&,
const enable_shared_from_this<_Tp1>*,
const _Tp2*) noexcept;
mutable weak_ptr<_Tp> _M_weak_this;
};
可以看到 enable_shared_from_this 模板類提供兩個(gè) public 屬性的 shared_from_this 成員函數(shù)。這兩個(gè)成員函數(shù)內(nèi)部會(huì)通過 _M_weak_this 成員來創(chuàng)建 shared_ptr 。其中 _M_weak_assign 函數(shù)不能手動(dòng)調(diào)用,這個(gè)函數(shù)會(huì)被 shared_ptr 自動(dòng)調(diào)用,用處是來初始化唯一的成員變量 _M_weak_this 。
分析:根據(jù)對象生成順序,先初始化基類 enable_shared_from_this ,再初始化派生類 Frame 對象本身,這時(shí) Frame 對象已經(jīng)生成,但 _M_weak_this 成員還未被初始化,最后應(yīng)通過 shared_ptr
更深層次:這個(gè) enable_shared_from_this 中有一個(gè)弱指針 weak_ptr ,這個(gè)弱指針能夠監(jiān)視 this 指針,在調(diào)用 shared_from_this 這個(gè)函數(shù)時(shí),這個(gè)函數(shù)內(nèi)部實(shí)際上是調(diào)用 weak_ptr 的 lock 方法,lock() 會(huì)讓 shared_ptr 指針計(jì)數(shù) +1,同時(shí)返回這個(gè) shared_ptr。
參考:
第21課 shared_ptr共享型智能指針 - 淺墨濃香 - 博客園 (cnblogs.com)
【C++】shared_ptr共享型智能指針詳解-CSDN博客
柚子快報(bào)激活碼778899分享:開發(fā)語言 【C++】share
相關(guān)文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。