weak_ptr的用處_weak_ptr的作用_kyowill的博客-CSDN博客
循環(huán)引用問題
#include <memory>
class B; // 前向聲明
class A {
public:
std::shared_ptr<B> b_ptr;
};
class B {
public:
std::shared_ptr<A> a_ptr;
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
return 0;
}
見上圖,當 main 函數(shù)結(jié)束時塔粒,a 和 b 這兩個共享指針會被銷毀,但是因為 a->b_ptr 和 b->a_ptr 的共享指針引用仍然存在,所以 A 和 B 對象共享指針引用計數(shù)都從2變?yōu)?静袖,于是這兩個對象都沒有正常銷毀
為了解決循環(huán)引用的問題房交,可以使用 std::weak_ptr
盅惜,它可以避免形成循環(huán)引用徐绑,因為** std::weak_ptr
不會增加引用計數(shù)**邪驮。只有當需要使用對象時,可以將 std::weak_ptr
轉(zhuǎn)換成 std::shared_ptr
傲茄,如果對象已經(jīng)被銷毀耕捞,則轉(zhuǎn)換會失敗。
以下是使用 std::weak_ptr
改進上述代碼的示例:
#include <memory>
class B; // Forward declaration
class A {
public:
std::shared_ptr<B> b_ptr;
};
class B {
public:
std::weak_ptr<A> a_ptr; // Using weak_ptr here
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
return 0;
}
這樣烫幕,A
和 B
之間的循環(huán)引用就不會造成內(nèi)存泄漏。
具體說明 std::weak_ptr
轉(zhuǎn)換成 std::shared_ptr
然后使用敞映?
當你需要使用 std::weak_ptr
指向的對象時较曼,你可以通過調(diào)用 std::weak_ptr
的 lock()
成員函數(shù)來嘗試將它轉(zhuǎn)換為一個有效的 std::shared_ptr
。這個操作會檢查所指向的對象是否還存在振愿,如果存在捷犹,就會返回一個指向該對象的有效 std::shared_ptr
,否則返回一個空的 std::shared_ptr
冕末。
在你的代碼中萍歉,如果你需要使用 B
對象所指向的 A
對象,你可以這樣做:
#include <memory>
class B; // Forward declaration
class A {
public:
std::shared_ptr<B> b_ptr;
};
class B {
public:
std::weak_ptr<A> a_ptr; // Using weak_ptr here
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
// Using weak_ptr to access the object
std::shared_ptr<A> a_from_b = b->a_ptr.lock();
if (a_from_b) {
// You can safely use a_from_b here
} else {
// The object has been destroyed
}
return 0;
}
在上面的代碼中档桃,通過調(diào)用 b->a_ptr.lock()
枪孩,你可以獲得一個指向 A
對象的 std::shared_ptr
,如果 A
對象仍然存在的話藻肄。如果 A
對象已經(jīng)被銷毀蔑舞,a_from_b
將會是一個空的 std::shared_ptr
。這樣你就可以在使用對象之前檢查它是否還存在嘹屯,避免懸空指針的問題攻询。
注意
- 雖然通過弱引用指針可以有效的解除循環(huán)引用,但這種方式必須在能預見會出現(xiàn)循環(huán)引用的情況下才能使用州弟,即這個僅僅是一種編譯期的解決方案钧栖,如果程序在運行過程中出現(xiàn)了循環(huán)引用低零,還是會造成內(nèi)存泄漏的。因此拯杠,不要認為只要使用了智能指針便能杜絕內(nèi)存泄漏掏婶。
具體說明
weak_ptr是為配合shared_ptr而引入的一種智能指針,它更像是shared_ptr的一個助手阴挣,而不是一個智能指針气堕。它的最大作用在于協(xié)助shared_ptr工作,像旁觀者那樣觀測資源的使用情況畔咧。weak_ptr被設(shè)計為與shared_ptr協(xié)同工作茎芭,可以從一個shared_ptr或者另外一個weak_ptr對象構(gòu)造,獲得資源的觀測權(quán)限誓沸。但weak_ptr沒有共享資源梅桩,它的構(gòu)造不會引入指針引用技術(shù)的增加,同樣拜隧,weak_ptr析構(gòu)時也不會導致引用技術(shù)減少宿百,它只是一個靜靜的觀察著。weak_ptr的一個很重要作用是:打破循環(huán)引用洪添。
讓我們一步步來分析循環(huán)引用問題垦页。
- SharedPtrNode
#ifndef SHAREPTRNODE_H_
#define SHAREPTRNODE_H_
#include <iostream>
using namespace std;
#include <boost/smart_ptr.hpp>
using namespace boost;
class SharePtrNode {
public :
int spNodeId;
SharePtrNode();
SharePtrNode( int nId);
virtual ~SharePtrNode();
typedef boost::shared_ptr<SharePtrNode> ptr_type;
ptr_type next;
};
#endif /* SHAREPTRNODE_H_ */
#include "SharePtrNode.h"
#include <iomanip> //for setw and setfill
SharePtrNode::SharePtrNode():SharePtrNode(0) {
// TODO Auto-generated constructor stub
}
SharePtrNode::SharePtrNode( int nId)
{
spNodeId = nId;
cout << "SharePtrNode, [spNodeId:"
<< setw(4) << setfill( '0' ) << spNodeId << "]" << endl;
}
SharePtrNode::~SharePtrNode() {
// TODO Auto-generated destructor stub
cout << "~SharePtrNode, [spNodeId:"
<< setw(4) << setfill( '0' ) << spNodeId << "]" << endl;
}
一個SharePtrNode對象包含一個自身id(spNodeId)和一個指針:ptr_type next。這個指針是一個shared_ptr類型的指針干奢∪福基于上述SharePtrNode類,我們構(gòu)建一個簡單的使用場景:動態(tài)創(chuàng)建兩個SharePtrNode對象忿峻。
void Case03_ShareAndWeakPtrLoopRef_1()
{
auto sharedPtrNode1 = boost::make_shared<SharePtrNode>(1);
auto sharedPtrNode2 = boost::make_shared<SharePtrNode>(2);
}
由于是通過shared_ptr管理的兩個對象薄啥,因此,兩個對象會在函數(shù)退出時自動銷毀逛尚,且銷毀的順序與創(chuàng)建的順序相反垄惧。
——> Case03_ShareAndWeakPtrLoopRef_1
SharePtrNode, [spNodeId:0001]
SharePtrNode, [spNodeId:0002]
~SharePtrNode, [spNodeId:0002]
~SharePtrNode, [spNodeId:0001]
這種場景下一切似乎都非常合理。
引用示意圖如下:
那如果對case場景做進一步補充:將sharedPtrNode1的next指針指向sharedPtrNode2绰寞,會產(chǎn)生什么結(jié)果呢到逊?
void Case03_ShareAndWeakPtrLoopRef_2()
{
auto sharedPtrNode1 = boost::make_shared<SharePtrNode>(1);
auto sharedPtrNode2 = boost::make_shared<SharePtrNode>(2);
sharedPtrNode1->next = sharedPtrNode2;
}
這個時候,會有什么輸出出現(xiàn)呢克握?
——> Case03_ShareAndWeakPtrLoopRef_2
SharePtrNode, [spNodeId:0001]
SharePtrNode, [spNodeId:0002]
~SharePtrNode, [spNodeId:0001]
~SharePtrNode, [spNodeId:0002]
順序變了蕾管,為什么會這樣?因為在執(zhí)行sharedPtrNode1->next = sharedPtrNode2;的時候?qū)嶋H上SharedPtrNode2的引用計數(shù)已經(jīng)從1增加到了2菩暗,當函數(shù)退出時掰曾,首先SharedPtrNode2先析構(gòu),此時SharedPtrNode2的引用技術(shù)從2減回到1停团,因為不到0旷坦,因此實際的SharedPtrNode2此時還不能銷毀掏熬,析構(gòu)函數(shù)沒有被調(diào)用。而此后SharedPtrNode1進行析構(gòu)秒梅,此時因為SharedPtrNode1的引用計數(shù)原本只有1旗芬,此時降低到0,因此SharedPtrNode1的析構(gòu)函數(shù)被調(diào)用:“~SharePtrNode, [spNodeId:0001]”捆蜀,在SharedPtrNode1進行析構(gòu)的時候疮丛,SharedPtrNode1的成員變量sharedPtrNode1->next也會一并析構(gòu),此時由于next指向的節(jié)點引用計數(shù)已經(jīng)是1了辆它,析構(gòu)的時候誊薄,變成0,因此執(zhí)行最終指向節(jié)點的SharePtrNode2的析構(gòu)锰茉,因此“~SharePtrNode, [spNodeId:0002]”輸出呢蔫。這也就是為什么node2在node1之后析構(gòu)的原因。
引用示意圖如下:
- 共享指針就是用來管理對象的生命周期的飒筑,對象的是否析構(gòu)要看指向該對象的共享指針的引用計數(shù)是否降為0片吊,為0則析構(gòu),否則不析構(gòu)协屡。(注意區(qū)分共享指針對象的析構(gòu)≠共享指針指向?qū)ο蟮奈鰳?gòu))
- 析構(gòu)時俏脊,指向B的共享指針對象b先析構(gòu),此時指向B的共享指針引用計數(shù)由2變?yōu)?肤晓,但不是0联予,所以B對象的析構(gòu)函數(shù)未被調(diào)用
- 而后,指向A的共享指針對象a先析構(gòu)材原,此時指向A的共享指針引用計數(shù)由1變?yōu)?,于是A的析構(gòu)函數(shù)被調(diào)用
- 由于對象A的析構(gòu)季眷,其成員——指向B對象的共享指針對象a被析構(gòu)余蟹,此時B的共享指針引用計數(shù)由1變?yōu)?,這時B的析構(gòu)函數(shù)被調(diào)用
- 這也就解釋了上述的析構(gòu)函數(shù)調(diào)用順序問題
在進一步構(gòu)建一個更為復雜一點的場景:再sharedPtrNode2的next指針指向sharedPtrNode1子刮,會產(chǎn)生什么結(jié)果呢威酒?
void Case03_ShareAndWeakPtrLoopRef_3()
{
auto sharedPtrNode1 = boost::make_shared<SharePtrNode>(1);
auto sharedPtrNode2 = boost::make_shared<SharePtrNode>(2);
sharedPtrNode1->next = sharedPtrNode2;
sharedPtrNode2->next = sharedPtrNode1;
}
我們可以看到,此時的輸出結(jié)果如下:
——> Case03_ShareAndWeakPtrLoopRef_3
SharePtrNode, [spNodeId:0001]
SharePtrNode, [spNodeId:0002]
兩個對象都值進行了初始化挺峡,而沒有進行析構(gòu)葵孤!
這里可參見開頭的解釋。