網(wǎng)站首頁 編程語言 正文
共享指針的初始化方式
1.裸指針直接初始化,但不能通過隱式轉(zhuǎn)換來構(gòu)造
2.允許移動構(gòu)造,也允許拷貝構(gòu)造
3.通過make_shared構(gòu)造
例:
#include <iostream> #include <memory> class Frame {}; int main() { std::shared_ptr<Frame> f(new Frame()); // 裸指針直接初始化 std::shared_ptr<Frame> f1 = new Frame(); // Error,explicit禁止隱式初始化 std::shared_ptr<Frame> f2(f); // 拷貝構(gòu)造函數(shù) std::shared_ptr<Frame> f3 = f; // 拷貝構(gòu)造函數(shù) f2 = f; // copy賦值運算符重載 std::cout << f3.use_count() << " " << f3.unique() << std::endl; std::shared_ptr<Frame> f4(std::move(new Frame())); // 移動構(gòu)造函數(shù) std::shared_ptr<Frame> f5 = std::move(new Frame()); // Error,explicit禁止隱式初始化 std::shared_ptr<Frame> f6(std::move(f4)); // 移動構(gòu)造函數(shù) std::shared_ptr<Frame> f7 = std::move(f6); // 移動構(gòu)造函數(shù) std::cout << f7.use_count() << " " << f7.unique() << std::endl; std::shared_ptr<Frame[]> f8(new Frame[10]()); // Error,管理動態(tài)數(shù)組時,需要指定刪除器 std::shared_ptr<Frame> f9(new Frame[10](), std::default_delete<Frame[]>()); auto f10 = std::make_shared<Frame>(); // std::make_shared來創(chuàng)建 return 0; }
注意:
1.盡量避免將一個裸指針傳遞給std::shared_ptr的構(gòu)造函數(shù),常用的替代手法是使用std::make_shared。如果必須將一個裸指針傳遞給shared_ptr的構(gòu)造函數(shù),就直接傳遞new運算符的結(jié)果,而非傳遞一個裸指針變量。
2.不要將this指針返回給shared_ptr。當(dāng)希望將this指針托管給shared_ptr時,類需要繼承自std::enable_shared_from_this,并且從shared_from_this()中獲得shared_ptr指針。
3.不要使用相同的原始指針作為實參來創(chuàng)建多個shared_ptr對象,具體原因見下面講的shared_ptr內(nèi)存模型??梢允褂每截悩?gòu)造或者直接使用重載運算符=進行操作
例:
#include <iostream> #include <memory> class Frame {}; int main() { Frame* f1 = new Frame(); std::shared_ptr<Frame> f2(f1); std::shared_ptr<Frame> f3(f1); // Error std::shared_ptr<Frame> f4(f2); auto f5 = f2; return 0; }
常用成員函數(shù)
s.get():返回shared_ptr中保存的裸指針;
s.reset(…):重置shared_ptr;
- reset( )不帶參數(shù)時,若智能指針s是唯一指向該對象的指針,則釋放,并置空。若智能指針P不是唯一指向該對象的指針,則引用計數(shù)減少1,同時將P置空。
- reset( )帶參數(shù)時,若智能指針s是唯一指向?qū)ο蟮闹羔槪瑒t釋放并指向新的對象。若P不是唯一的指針,則只減少引用計數(shù),并指向新的對象。如:
auto s = make_shared<int>(100); s.reset(new int (200));
s.use_count()
:返回shared_ptr的強引用計數(shù);
s.unique()
:若use_count()為1,返回true,否則返回false。
具體實例:
auto pointer = std::make_shared<int>(10); auto pointer2 = pointer; // 引用計數(shù)+1 auto pointer3 = pointer; // 引用計數(shù)+1 int *p = pointer.get(); // 這樣不會增加引用計數(shù) std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3 std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3 std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3 pointer2.reset(); std::cout << "reset pointer2:" << std::endl; std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2 std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0, pointer2 已 reset std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2 pointer3.reset(); std::cout << "reset pointer3:" << std::endl; std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1 std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0 std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 0, pointer3 已 reset
shared_ptr內(nèi)存模型
由圖可以看出,shared_ptr包含了一個指向?qū)ο蟮闹羔樅鸵粋€指向控制塊的指針。每一個由shared_ptr管理的對象都有一個控制塊,它除了包含強引用計數(shù)、弱引用計數(shù)之外,還包含了自定義刪除器的副本和分配器的副本以及其他附加數(shù)據(jù)。
控制塊的創(chuàng)建規(guī)則
- std::make_shared總是創(chuàng)建一個控制塊;
- 從具備所有權(quán)的指針出發(fā)構(gòu)造一個std::shared_ptr時,會創(chuàng)建一個控制塊(如std::unique_ptr轉(zhuǎn)為shared_ptr時會創(chuàng)建控制塊,因為unique_ptr本身不使用控制塊,同時unique_ptr置空);
- 當(dāng)std::shared_ptr構(gòu)造函數(shù)使用裸指針作為實參時,會創(chuàng)建一個控制塊。這意味從同一個裸指針出發(fā)來構(gòu)造不止一個std::shared_ptr時會創(chuàng)建多重的控制塊,也意味著對象會被析構(gòu)多次。如果想從一個己經(jīng)擁有控制塊的對象出發(fā)創(chuàng)建一個std::shared_ptr,可以傳遞一個shared_ptr或weak_ptr而非裸指針作為構(gòu)造函數(shù)的實參,或者直接使用重載運算符=,這樣則不會創(chuàng)建新的控制塊。
因此,更好的解決方式是盡量避免使用裸指針作為共享指針的實參,而是使用make_shared,此外,make_shared相比直接new還具有以下好處
make_shared的優(yōu)缺點
優(yōu)點
- 避免代碼冗余:創(chuàng)建智能指針時,被創(chuàng)建對象的類型只需寫1次,而用new創(chuàng)建智能指針時,需要寫2次;
- 異常安全:make系列函數(shù)可編寫異常安全代碼,改進了new的異常安全性;
- 提升性能:編譯器有機會利用更簡潔的數(shù)據(jù)結(jié)構(gòu)產(chǎn)生更小更快的代碼。使用make_shared時會一次性進行內(nèi)存分配,該內(nèi)存單塊(single chunck)既保存了T對象又保存與其相關(guān)聯(lián)的控制塊。而直接使用new表達式,除了為T分配一次內(nèi)存,還要為與其關(guān)聯(lián)的控制塊再進行一次內(nèi)存分配。
make_shared與new方式內(nèi)存分布對比圖:
缺點
- 所有的make系列函數(shù)都不允許自定義刪除器;
- make系列函數(shù)創(chuàng)建對象時,不能接受{}初始化列表(這是因為完美轉(zhuǎn)發(fā)的轉(zhuǎn)發(fā)函數(shù)是個模板函數(shù),它利用模板類型進行推導(dǎo)。因此無法將{}推導(dǎo)為initializer_list)。換言之,make系列只能將圓括號內(nèi)的形參完美轉(zhuǎn)發(fā);
- **自定義內(nèi)存管理的類(如重載了operator new和operator delete),不建議使用make_shared來創(chuàng)建。**因為:重載operator new和operator delete時,往往用來分配和釋放該類精確尺寸(sizeof(T))的內(nèi)存塊;而make_shared創(chuàng)建的shared_ptr,是一個自定義了分配器(std::allocate_shared)和刪除器的智能指針,由allocate_shared分配的內(nèi)存大小也不等于上述的尺寸,而是在此基礎(chǔ)上加上控制塊的大??;
- 對象的內(nèi)存可能無法及時回收。因為:make_shared只分配一次內(nèi)存,減少了內(nèi)存分配的開銷,使得控制塊和托管對象在同一內(nèi)存塊上分配。而控制塊是由shared_ptr和weak_ptr共享的,因此兩者共同管理著這個內(nèi)存塊(托管對象+控制塊)。當(dāng)強引用計數(shù)為0時,托管對象被析構(gòu)(即析構(gòu)函數(shù)被調(diào)用),但內(nèi)存塊并未被回收,只有等到最后一個weak_ptr離開作用域時,弱引用也減為0才會釋放這塊內(nèi)存塊。原本強引用減為0時就可以釋放的內(nèi)存, 現(xiàn)在變?yōu)榱藦娨煤腿跻枚紲p為0時才能釋放, 意外的延遲了內(nèi)存釋放的時間。這對于內(nèi)存要求高的場景來說, 是一個需要注意的問題。
引用計數(shù)
- shared_ptr中的引用計數(shù)直接關(guān)系到何時是否進行對象的析構(gòu),因此它的變動尤其重要。
- shared_ptr的**構(gòu)造函數(shù)會使該引用計數(shù)遞增,而析構(gòu)函數(shù)會使該計數(shù)遞減。**但移動構(gòu)造表示從一個己有的shared_ptr移動構(gòu)造到一個新的shared_ptr。這意味著一旦新的shared_ptr產(chǎn)生后,原有的shared_ptr會被置空,其結(jié)果是引用計數(shù)沒有變化;
- 拷貝賦值操作同時執(zhí)行兩種操作(如sp1和sp2是指向不同對象的shared_ptr,則執(zhí)行sp1=sp2時,將修改sp1使得其指向sp2所指的對象。而最初sp1所指向的對象的引用計數(shù)遞減,同時sp2所指向的對象引用計數(shù)遞增);
- reset函數(shù),如果不帶參數(shù)時,則引用計數(shù)減1。如果帶參數(shù)時,如sp.reset( p )則sp原來指向的對象引用計數(shù)減1,同時sp指向新的對象( p );
- 如果實施一次遞減后最后的引用計數(shù)變成0,即不再有shared_ptr指向該對象,則會被shared_ptr析構(gòu)掉;
- 引用計數(shù)的遞增和遞減是原子操作,即允許不同線程并發(fā)改變引用計數(shù)。
比較運算符
所有比較運算符都會調(diào)用共享指針內(nèi)部封裝的原始指針的比較運算符;支持==、!=、<、<=、>、>=;同類型的共享指針才能使用比較運算符
shared_ptr<int> sp_n1 = make_shared<int>(1); shared_ptr<int> sp_n2 = make_shared<int>(2); shared_ptr<int> sp_nu; shared_ptr<double> sp_d1 = make_shared<double>(1); bool bN1LtN2 = sp_n1 < sp_n2; //true bool bN1GtNu = sp_n1 > sp_nu; //true bool bNuEqNu = sp_nu == sp_nu; //true bool bN2GtD1 = sp_d1 < sp_n2; //編譯錯誤
總結(jié)
原文鏈接:https://blog.csdn.net/muguang629/article/details/123665583
相關(guān)推薦
- 2022-05-05 R語言因子類型的實現(xiàn)_R語言
- 2022-11-12 C語言大小端字節(jié)序存儲模式深入解讀_C 語言
- 2022-04-12 【debug】illegal hardware instruction
- 2022-05-13 kafka-server-stop.sh關(guān)閉Kafka失敗
- 2021-12-15 C/C++?Qt?數(shù)據(jù)庫與Chart歷史數(shù)據(jù)展示_C 語言
- 2022-09-05 Spark Rdd之mapToPair,flatMapToPair
- 2022-04-28 web.xml中Maven占位符不生效問題記錄分析_web2.0
- 2022-08-18 Docker搭建私有GitLab服務(wù)的方法_docker
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支