概 述
整理一下 shared_ptr 的相關(guān)技術(shù)以及其使用的注意項缓屠。
整 理
1. 用make_shared
創(chuàng)建智能指針
shared_ptr 內(nèi)部包含一個托管對象的原始指針以及一個引用計數(shù)癞揉,因此直接使用 new
來創(chuàng)建一個 shared_ptr 需要兩次內(nèi)存分配:一個用于托管對象复隆,另一個用于引用計數(shù),并且兩塊內(nèi)存空間是不連續(xù)的吞获。而make_shared
只需要分配一次內(nèi)存渔嚷,并在其中創(chuàng)建兩個,并且沒有暴露指向托管對象的原始指針:
// std::shared_ptr<int> p(new int); 改為:
std::shared_ptr<int> p = std::make_shared<int>();
因此make_shared
更高效媳握,對 CPU 緩存友好碱屁,且避免了內(nèi)存泄漏的任何可能性。
缺點
延遲了內(nèi)存釋放的時間:由于make_shared
只分配一次內(nèi)存蛾找,所以將對象的生命周期與控制塊(強引用娩脾、弱引用的信息)的生命周期捆綁到一起,原本強引用計數(shù)為零時就可以釋放內(nèi)存打毛,現(xiàn)在變?yōu)榱藦娨檬辽蕖⑷跻枚紴榱銜r才能釋放,延遲了內(nèi)存釋放的時間
2. 減少 shared_pt 的拷貝
正如上文所說幻枉,shared_ptr 的內(nèi)存占用是原始指針的兩倍碰声,因此 shared_ptr 的拷貝開銷比拷貝原始指針要高。
- 使用
std::move()
來移動 shared_ptr 的所有權(quán)
// 當(dāng)一個 shared_ptr 需要將其所有權(quán)轉(zhuǎn)移給另外一個新的 shared_ptr 時
std::shared_ptr<int> p1 = std::make_shared<int>();
// 使用 std::move()熬甫,減少拷貝
std::shared_ptr<int> p2 = std::move(p1); //此時 p1 等于 nullptr !
- 以 const reference 方式傳遞
void add(const shared_ptr<number>& pNumber);
void subtract(const shared_ptr<number>& pNumber);
// 傳常引用(pass by const reference)胰挑,減少拷貝
void func(const int& num)
{
auto pNumber = std::make_shared<number>(num);
add(pNumber);
subtract(pNumber);
}
3. enable_shared_from_this
當(dāng)我們使用異步回調(diào)時,通常會需要傳入當(dāng)前類對象椿肩,或者使用類成員函數(shù)作為回調(diào)瞻颂。例如:
class obj
{
// ...
};
void obj::Init()
{
m_pAsyncTimer->SetCallback(std::bind(&obj::Func, this, std::placeholders::_1));
};
如果直接使用this
來傳入,無法保證在回調(diào)函數(shù)中操作的this
對象依然有效郑象。
對此贡这,我們可以使用enable_shared_from_this
來使得當(dāng)前對象this
變?yōu)?shared_ptr,從而延長對象的生命周期厂榛,保證在異步回調(diào)時盖矫,對象依然有效丽惭。
class obj : public std::enable_shared_from_this<obj>
{
// ...
};
void obj::Init()
{
m_pAsyncTimer->SetCallback(std::bind(&obj::Func, shared_from_this(), std::placeholders::_1));
};
注:使用shared_from_this()
需要對象在堆上創(chuàng)建,并且由 shared_ptr 管理其生命周期炼彪。因此shared_from_this()
不能在構(gòu)造函數(shù)中調(diào)用吐根,因為在構(gòu)造對象時,其還沒有交給 shared_ptr 接管辐马。
此外拷橘,對于在類的成員函數(shù)中需要把當(dāng)前類對象作為參數(shù)傳遞給其他函數(shù)時,如果直接傳遞this
喜爷,當(dāng)前對象就會被多個 shared_ptr 管理冗疮,造成二次釋放的錯誤。使用enable_shared_from_this
配合shared_from_this()
也可以很好的解決此問題檩帐。
4. 弱回調(diào)
上文提到的shared_from_this()
延長了對象的生命周期术幔,如果我們不想額外延長對象的生命周期。例如針對如果對象還活著湃密,就調(diào)用它的回調(diào)函數(shù)诅挑,反之忽略這樣的需求,我們可以使用 weak_ptr 來傳入std::function
泛源,這樣就不會延長對象的生命周期拔妥。在回調(diào)時,嘗試提升為 shared_ptr达箍,如果成功没龙,說明對象有效,那么執(zhí)行回調(diào)缎玫;反之則忽略硬纤。
5. 自定義析構(gòu)
shared_ptr 的構(gòu)造函數(shù)允許傳入自定義刪除器Deleter
template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );
因此,shared_ptr 支持自定義析構(gòu)動作赃磨。在繼承關(guān)系中筝家,使用 shared_ptr 管理,虛析構(gòu)不再是必須的邻辉。
需要注意的是肛鹏,上文提到的make_shared
構(gòu)造智能指針不允許自定義刪除器,刪除器Deleter
支持std::function
對象
#include <memory>
#include <functional>
class Sample {
public:
// ...
};
void deleter(Sample* x) {
// ...
}
int main() {
std::shared_ptr<Sample> p1(new Sample, deleter);
std::shared_ptr<Sample> p2(new Sample, [](Sample* x) {
// ...
});
std::shared_ptr<Sample> p3(new Sample, std::bind(deleter, std::placeholders::_1));
return 0;
}
6. 析構(gòu)所在的線程
對象的析構(gòu)是同步的恩沛,當(dāng)指向?qū)ο蟮淖詈笠粋€ shared_ptr 離開作用域時,對象就會在當(dāng)前線程析構(gòu)缕减。這有可能會影響到關(guān)鍵線程的速度雷客,對此可以用一個單獨的線程來專門做析構(gòu)。
由于 shared_ptr<void> 可以持有任何對象并安全的釋放桥狡,我們可以通過一個BlockingQueue<shared_ptr<void>>
(shared_ptr<void>的阻塞隊列)把對象的析構(gòu)都轉(zhuǎn)移到專用線程搅裙,從而解放關(guān)鍵線程皱卓。
7. 循環(huán)引用
若A持有B的 shared_ptr,且B也持有A的 shared_ptr部逮,此時會造成循環(huán)引用娜汁,導(dǎo)致A、B都無法釋放兄朋。此時掐禁,我們可以使用weak_ptr
。
weak_ptr
不會增加引用計數(shù)颅和,因此可以打破 shared_ptr 的循環(huán)引用傅事。通常在繼承關(guān)系中的做法是父類持有子類的 shared_ptr,子類持有指向父類的 weak_ptr峡扩。
參考:
《Linux多線程服務(wù)端編程》陳碩
https://stackoverflow.com/questions/18301511
https://stackoverflow.com/questions/712279
https://zh.cppreference.com/w/cpp/memory/shared_ptr/make_shared