第 12 章 動(dòng)態(tài)內(nèi)存
概述. 動(dòng)態(tài)內(nèi)存和智能指針
在c++中,,動(dòng)態(tài)內(nèi)存的管理是通過一對(duì)運(yùn)算符來完成的:
new,,在動(dòng)態(tài)內(nèi)存中為對(duì)象分配空間并返回一個(gè)指向該對(duì)象的指針,。我們可以選擇對(duì)對(duì)象進(jìn)行初始化
delete,,接受一個(gè)動(dòng)態(tài)對(duì)象的指針,銷毀該對(duì)象,,并釋放與之相關(guān)的內(nèi)存,。
動(dòng)態(tài)分配內(nèi)存帶來了許多問題,,比如忘記釋放的內(nèi)存泄漏,提前釋放的指針非法訪問內(nèi)存,。
c++11新標(biāo)準(zhǔn)庫提供了兩種智能指針類型來管理動(dòng)態(tài)對(duì)象,,只能指針的行為類似常規(guī)指針,,區(qū)別是它自動(dòng)釋放所指向的內(nèi)存。
頭文件#include <memory>
兩種智能指針:
shared_ptr:允許多個(gè)指針指向同一個(gè)對(duì)象,。
unique_ptr:獨(dú)占所指向的對(duì)象,。
伴隨類weak_ptr:指向share_ptr所管理的對(duì)象。
1.share_ptr類:
- //shared_ptr和unique_ptr都支持的操作
- //空智能指針,??梢灾赶騭tring類型的對(duì)象
- shared_ptr<string>sp;
- unique_ptr<string>up;
- sp //sp可以作為條件判斷sp是否指向一個(gè)對(duì)象
- *sp //解引用sp,獲得它指向的對(duì)象
- sp->mem //等價(jià)于(*sp).mem
- sp.get() //返回sp中所報(bào)存的指針。要小心使用,,所智能指針釋放了對(duì)象,,則返回的指針?biāo)赶虻膶?duì)象也不存在了。
- swap(sp,sq)
- sp.swap(sq) //交換sp和sq中的指針
-
- //shared_ptr支持的操作
- make_shared<T>(args) //返回一個(gè)shared_ptr,指向一個(gè)動(dòng)態(tài)分配的類型為T的對(duì)象,,使用args初始化對(duì)象
- shared_ptr<T>p(q) //p是shared_ptr q的拷貝,,此操作會(huì)遞增q中的記數(shù)器,q中的指針必須能轉(zhuǎn)換成T*
- p = q //p和q都是shared_ptr,,所保存的指針必須能相互轉(zhuǎn)換,,此操作會(huì)遞減p的引用計(jì)數(shù),增加q的引用計(jì)數(shù),,p引用計(jì)數(shù)為0時(shí)會(huì)釋放其管理的內(nèi)存,。
- p.use_count() //返回與p共享智能指針的數(shù)量,可能很慢主要用于調(diào)試
- p.unique() //當(dāng)p.use_count()為1時(shí),,返回ture,否則返回false,。
注意:
智能指針比較所指對(duì)象是否相同,只能通過get( )返回指針,,比較指針的地址是否相等來判斷,。
<1.make_shared函數(shù)#include <memory>
make_shared是一個(gè)非成員函數(shù),具有給共享對(duì)象分配內(nèi)存,,并且只分配一次內(nèi)存的優(yōu)點(diǎn),,和顯式通過構(gòu)造函數(shù)初始化(new)的shared_ptr相比較,后者需要至少兩次分配內(nèi)存,。這些額外的開銷有可能會(huì)導(dǎo)致內(nèi)存溢出的問題
最安全的使用動(dòng)態(tài)內(nèi)存的方法是使用一個(gè)make_shared的函數(shù)。
此函數(shù)在動(dòng)態(tài)內(nèi)存中分配一個(gè)對(duì)象并初始化,,返回指向此對(duì)象的shared_ptr,。
我們可以認(rèn)為每個(gè)shared_ptr都有一個(gè)關(guān)聯(lián)的計(jì)數(shù)器,通常稱其為引用計(jì)數(shù),,無論我們拷貝一個(gè)share_ptr,,計(jì)數(shù)器都會(huì)遞增。
當(dāng)我們給一個(gè)shared_ptr賦值或者shared被銷毀,,計(jì)數(shù)器就會(huì)遞減,。
當(dāng)用一個(gè)shared_ptr初始化另外一個(gè)shared_ptr,,或?qū)⑺鳛閰?shù)傳遞給一個(gè)函數(shù)以及作為函數(shù)的返回值(賦值給其他的),計(jì)數(shù)器都會(huì)遞增
一旦一個(gè)share_ptr的計(jì)數(shù)器變?yōu)?,,它就會(huì)釋放自己所管理的對(duì)象,。
!注意標(biāo)準(zhǔn)庫是用計(jì)數(shù)器還是其他數(shù)據(jù)結(jié)構(gòu)來記錄有多少個(gè)指針共享對(duì)象由標(biāo)準(zhǔn)庫來決定,,關(guān)鍵是智能指針類能記錄有多少個(gè)shared_ptr指向相同的對(duì)象,,
并能在恰當(dāng)?shù)臅r(shí)候自動(dòng)釋放對(duì)象。
補(bǔ)充:智能指針和make_shared分配內(nèi)存初始化
- #include <iostream>
- #include <string>
- #include <vector>
- #include <memory>
-
- using namespace std;
-
- int main()
- {
- shared_ptr<string>sp;
- make_shared<string>(); //動(dòng)態(tài)分配內(nèi)存默認(rèn)初始化,,必須要有括號(hào)
- //make_shared<string>ms; //error:動(dòng)態(tài)分配內(nèi)存?。÷?lián)想c語言malloc也沒有起名字 - -,。
- make_shared<string>("a"); //動(dòng)態(tài)分配內(nèi)存值初始化
- shared_ptr<string>sp2 = make_shared<string>(); //初始化智能指針
- shared_ptr<string>sp3 = make_shared<string>("b");//初始化智能指針
- }
例子:
- #include <iostream>
- #include <memory>
- #include <string>
-
- using namespace std;
-
- shared_ptr<string> fun1(shared_ptr<string> sp5)//傳遞參數(shù)會(huì)構(gòu)造一個(gè),,計(jì)數(shù)器遞增,函數(shù)運(yùn)行結(jié)束后釋放
- {
- auto sp6 = sp5; //創(chuàng)建臨時(shí)并賦值,,計(jì)數(shù)器遞增,。
- cout << "sp5 use_count:" << sp5.use_count() << endl;
- cout << "sp5 is_unique:" << sp5.unique() << endl;
- return sp6;
- }
-
- int main()
- {
- shared_ptr<string>sp = make_shared<string>("aa");
- auto sp3 = make_shared<string>(10,'a');//通常使用auto來簡(jiǎn)化定義一個(gè)對(duì)象來保存make_shared的結(jié)果,這種方式比較簡(jiǎn)單,。
- cout << "sp use_count:" << sp.use_count() << endl;
- auto sp2(sp); //拷貝sp,count計(jì)數(shù)會(huì)增加
- cout << "sp use_count:" << sp.use_count() << endl;
- cout << "sp is_unique:" << sp.unique() << endl;
- sp2 = sp3; //賦值sp2,計(jì)數(shù)會(huì)減少
- cout << "sp use_count:" << sp.use_count() << endl;
- cout << "sp is_unique:" << sp.unique() << endl;
- auto sp4(sp3);
- cout << "sp3 use_count:" << sp3.use_count() << endl;
- cout << "sp3 is_unique:" << sp3.unique() << endl;
- sp = sp3;//sp指向sp3指向的,sp指向的被銷毀,。
- cout << "sp use_count:" << sp.use_count() << endl;
- cout << "sp is_unique:" << sp.unique() << endl;
- auto sp7 = fun1(sp);
- cout << "sp7 use_count:" << sp.use_count() << endl;
- cout << "sp7 is_unique:" << sp.unique() << endl;
-
- }
注意! sp1 = sp2; sp2計(jì)數(shù)器值增加,,右值的計(jì)數(shù)器增加,,左值指向的對(duì)象的計(jì)數(shù)器減少,減少為0時(shí)自動(dòng)釋放對(duì)象
<2.shared_ptr 自動(dòng)銷毀所管理的對(duì)象
當(dāng)指向?qū)ο蟮淖詈笠粋€(gè)shared_ptr 被銷毀時(shí),,shared_ptr 類會(huì)自動(dòng)銷毀此對(duì)象,。它是通過特殊的成員函數(shù)析構(gòu)函數(shù)來控制對(duì)象銷毀時(shí)做什么操作。
shared_ptr 的析構(gòu)函數(shù)會(huì)遞減它所指向的對(duì)象的引用計(jì)數(shù),,如果引用計(jì)數(shù)變?yōu)?,,shared_ptr 的函數(shù)就會(huì)銷毀對(duì)象,并釋放它占用的資源,。
對(duì)于一塊內(nèi)存,,shared_ptr 類保證只要有任何shared_ptr 對(duì)象引用它,它就不會(huì)被釋放,。
如果我們忘記了銷毀程序不再需要的shared_ptr,,程序仍然會(huì)正確運(yùn)行,但會(huì)浪費(fèi)內(nèi)存
注意?。喝绻銓hared_ptr存放于一個(gè)容器中,,而后不在需要全部元素,而只使用其中的一部分,要記得調(diào)用erase刪除不再需要的那些元素,。
注意?。簩⒁粋€(gè)shared_ptr 賦予另一個(gè)shared_ptr 會(huì)遞增賦值號(hào)右側(cè)的shared_ptr 的引用計(jì)數(shù),而遞減左側(cè)shared_ptr 的引用計(jì)數(shù),,如果一個(gè)shared_ptr 引用技術(shù)
變?yōu)?時(shí),,它所指向的對(duì)象會(huì)被自動(dòng)銷毀。
<3.使用了動(dòng)態(tài)生存期的資源的類
程序使用動(dòng)態(tài)內(nèi)存出于以下三種原因
1.程序不知道自己需要使用多少對(duì)象
2.程序不知道所需對(duì)象的準(zhǔn)確類型
3.程序需要在多個(gè)對(duì)象間共享數(shù)據(jù),。
使用動(dòng)態(tài)內(nèi)存的一個(gè)常見的原因是允許多個(gè)對(duì)象共享相同的狀態(tài),。
重點(diǎn)例子!,!
我們希望定義一個(gè)Blob類,,保存一組元素,與容器不同,,我們希望Blob對(duì)象的不同拷貝之間共享相同的元素,。既當(dāng)我們拷貝一個(gè)Blob時(shí),
原Blob對(duì)象及其拷貝應(yīng)該引用相同的底層元素,。
定義一個(gè)管理string的類,,命名為StrBlob。
- #include <iostream>
- #include <string>
- #include <memory> //智能指針和動(dòng)態(tài)分配內(nèi)存
- #include <vector>
- #include <initializer_list> //初始值列表
- #include <stdexcept>
-
- class StrBlob
- {
- public:
- typedef std::vector<std::string>::size_type size_type;
- StrBlob();
- StrBlob(std::initializer_list<std::string>il);
- size_type size()const{ return data->size(); }
- bool empty() { return data->empty(); }
- //添加刪除元素
- void push_back(const std::string &s){ data->push_back(s); }
- void pop_back();
- //訪問元素
- std::string& front();
- std::string& back();
- const std::string& front()const;
- const std::string& back() const;
-
- private:
- std::shared_ptr<std::vector<std::string>> data;
- //private 檢查函數(shù),。
- void check(size_type i, const std::string &msg)const;
- };
-
- //默認(rèn)構(gòu)造函數(shù)
- StrBlob::StrBlob():
- data(std::make_shared<std::vector<std::string>>()) { }
- //拷貝構(gòu)造函數(shù)
- StrBlob::StrBlob(std::initializer_list<std::string>il):
- data(std::make_shared<std::vector<std::string>>(il)) { }
-
-
- void StrBlob::check(size_type i, const std::string &msg)const
- {
- if(i >= data->size())
- throw std::out_of_range(msg);
- }
-
- const std::string& StrBlob::back()const
- {
- check(0, "back on empty StrBlob");
- return data->back();
- }
- <span style="color:#FF0000;">
- //避免代碼重復(fù)和編譯時(shí)間問題,,用non-const版本調(diào)用const版本
- //在函數(shù)中必須先調(diào)用const版本,然后去除const特性
- //在調(diào)用const版本時(shí),,必須將this指針轉(zhuǎn)換為const,注意轉(zhuǎn)換的是this指針,,所以<>里面是const StrBlob* 是const的類的指針。
- //調(diào)用const版本時(shí)對(duì)象是const,所以this指針也是const,通過轉(zhuǎn)換this指針才能調(diào)用const版本,,否則調(diào)用的是non-const版本,,non-const調(diào)用non-const會(huì)引起無限遞歸。
- //return時(shí),,const_cast拋出去除const特性,。</span>
-
- std::string& StrBlob::back()
- {
- const auto &s = static_cast<const StrBlob*>(this)->back(); //<span style="color:#FF0000;">auto前面要加const,因?yàn)閍uto推倒不出來const。</span>
- return const_cast<std::string&>(s);
- }
-
- const std::string& StrBlob::front()const
- {
- check(0, "front on empty StrBlob");
- return data->front();
- }
-
- std::string& StrBlob::front()
- {
- const auto &s = static_cast<const StrBlob*>(this)->front();
- return const_cast<std::string&>(s);
- }
-
- void StrBlob::pop_back()
- {
- check(0, "pop_back on empty StrBlob");
- data->pop_back();
- }
-
- int main()
- {
- std::shared_ptr<StrBlob>sp;
- StrBlob s({"wang","wei","hao"});
- StrBlob s2(s);//共享s內(nèi)的數(shù)據(jù)
- std::string st = "asd";
- s2.push_back(st);
- //s2.front();
- std::cout << s2.front() << std::endl;
- std::cout << s2.back() << std::endl;
- }
可以輸出s和s2的size( )是相等的證明他們共享的是同一塊內(nèi)存,。
2.直接管理內(nèi)存
<1.使用new動(dòng)態(tài)分配和初始化對(duì)象
- #include <iostream>
- #include <string>
- #include <vector>
-
- using namespace std;
-
- int main()
- {
- //在自由空間分配的內(nèi)存是無名的,,因此new無法為其分配的對(duì)象命名,而是返回該指向該對(duì)象的一個(gè)指針,。
- int *p = new int;
- int *p2 = new int(10);
- string *p3 = new string(10,'a');
- vector<string> *p4 = new vector<string>{"a","b","c"};
- //也可以對(duì)動(dòng)態(tài)分配的對(duì)象進(jìn)行值初始化,,只需在類型名之后加一對(duì)空括號(hào)
- string *p5 = new string(); //值初始化
- string *p6 = new string; //默認(rèn)初始化
- int *p7 = new int; //但是對(duì)于內(nèi)置類型是未定義的。*p7值未定義
- //對(duì)動(dòng)態(tài)分配的對(duì)象進(jìn)行初始化通常是個(gè)好主意,。
-
- //auto
- auto p8 = new auto("abc");
- auto p9 = new auto{1,2,3}; //error ??
-
- //const
- //一個(gè)動(dòng)態(tài)分配的const對(duì)象必須進(jìn)行初始化,。
- const string *p10 = new const string("aha");
-
- //內(nèi)存耗盡
- int *p11 = new int; //如果分配失敗,new會(huì)拋出一個(gè)std::bad_alloc
- int *p12 = new (nothrow) int; //如果分配失敗返回一個(gè)空指針。 bad_alloc和nothrow定義在#include <new>
- }
<2.delete注意:
delete p,;釋放p所指向的對(duì)象的那塊內(nèi)存區(qū)域,,釋放后p仍然指向那塊區(qū)域(測(cè)試時(shí)輸出的地址仍然相同),但是釋放后輸出的對(duì)象已無效,。
一般我們可以在釋放delete p后, p = nullptr,。這樣明確指針不指向其他的區(qū)域。
空懸指針:指向一塊曾經(jīng)保存數(shù)據(jù)現(xiàn)在已經(jīng)無效的內(nèi)存的指針,。
堅(jiān)持只使用智能指針,,就可以避免所有的問題,對(duì)于一塊內(nèi)存,,只有在沒有任何智能指針指向它的情況下,,智能指針才會(huì)釋放它。
- #include <memory>
- #include <iostream>
-
- using namespace std;
-
- int main()
- {
- //p也指向p2指向的內(nèi)存,,那么p原來所指向的內(nèi)存區(qū)域就沒有其他指針指向了,,也沒有釋放,內(nèi)存泄漏
- int *p = new int(20);
- int *p2 = new int(40);
- p = p2;
- //p3也指向p4所指向的內(nèi)存,,但是p3原先指向的內(nèi)存區(qū)域計(jì)數(shù)器變?yōu)?時(shí),,內(nèi)存自動(dòng)會(huì)釋放。
- auto p3 = make_shared<int>(20);
- auto p4 = make_shared<int>(30);
- p3 = p4;
- }
<3.shared_ptr和new結(jié)合使用,。
<<1.
可以用new來初始化智能指針
接受指針參數(shù)的智能指針構(gòu)造函數(shù)是explicit(避免隱式轉(zhuǎn)換)的,,我們不能將一個(gè)內(nèi)置指針隱式轉(zhuǎn)換為一個(gè)智能指針。
默認(rèn)情況下,,一個(gè)用來初始化智能指針的普通指針必須指向一塊動(dòng)態(tài)分配的內(nèi)存,,因?yàn)橹悄苤羔樐J(rèn)使用delete釋放它所關(guān)聯(lián)的對(duì)象。
如果將智能指針綁定到其他類型的指針上,,我們必須自己定義自己的釋放操作,。
- #include <iostream>
- #include <memory>
-
- using namespace std;
-
- shared_ptr<int> clone(int p)
- {
- return shared_ptr<int>(new int(p));
- //return new int(p); error
- }
-
- int main()
- {
- shared_ptr<int>p(new int(10));
- cout << "p:" << p << endl;
- cout << "p:" << p.unique() << endl;
- cout << "p:" << p.use_count() << endl;
- //error: not int* transfrom shared_ptr<int>
- //shared_ptr<int>p2 = new int(10);
-
- //定義和改變shared_ptr的其他方法
- int *p2 = new int(20);
- shared_ptr<int>p3(p2);
- cout << "p3:" << *p3 << endl;
- cout << "p3:" << p3.unique() << endl;
- cout << "p3:" << p3.use_count() << endl;
- unique_ptr<int>p4(new int(40));
- //shared_ptr<int>p5(move(p4));//要添加move將p4轉(zhuǎn)換為右值。左值不行,。
- //cout << "p5:" << p5.unique() << endl;
- //cout << "p5:" << p5.use_count() << endl;
- p3.reset();//重置p3
- int *p6 = new int(50);
- p3.reset(p6);//重置p3并將p3綁定到p6,。
- cout << "p3:" << *p3 << endl;
- cout << "p3:" << p3.unique() << endl;
- cout << "p3:" << p3.use_count() << endl;
-
- //shared_ptr<T> p(q,d); d自己定義delete釋放內(nèi)存
- //shared_ptr<T> p(p2,d);
- //p.reset(); 釋放p對(duì)象,use_count()減了1,。
- //p.reset(q); 釋放p對(duì)象,,獲得q,q必須是內(nèi)置類型動(dòng)態(tài)分配的!
- //p.reset(q,d); 用d函數(shù)釋放p對(duì)象,,獲得q, (q必須是內(nèi)置類型動(dòng)態(tài)分配的,!)。
-
- }
<<2.不要混用智能指針和普通指針,。
shared_ptr 可以協(xié)調(diào)對(duì)象的析構(gòu)(也就是計(jì)數(shù)為0釋放),,僅限于自身的拷貝,所以推薦使用make_shared而不是new。
在分配對(duì)象時(shí)就將對(duì)象綁定在shared_ptr上面,。
當(dāng)將一個(gè)shared_ptr 綁定到一個(gè)普通指針時(shí),,我們就將內(nèi)存管理交給了shared_ptr,之后我們就不應(yīng)該使用內(nèi)置指針來訪問shared_ptr指向的內(nèi)存了,。
使用內(nèi)置指針來訪問智能指針?biāo)絼t的對(duì)象是非常危險(xiǎn)的,,我們不知道對(duì)象何時(shí)被銷毀。
<<3.也不要使用get初始化另一個(gè)智能指針或者為智能指針賦值
智能指針定義了一個(gè)名為get的函數(shù),,返回一個(gè)普通類型的指針,。
目的:向不能使用智能指針的代碼傳遞一個(gè)內(nèi)置指針,使用get返回的指針的代碼不能delete此指針,。
將另一個(gè)智能指針綁定到get返回的指針也是錯(cuò)誤的,。
永遠(yuǎn)不要用get初始化另一個(gè)智能指針或者為另一個(gè)智能指針賦值。
普通指針不能自動(dòng)轉(zhuǎn)化為智能指針,。
為什么使用get返回的指針不能使用delete
- {
- auto p = make_shared<int>(20);
- auto q = p.get();
- delete q;
- }
p是一個(gè)智能指針,,在函數(shù)塊結(jié)束后會(huì)自動(dòng)調(diào)用內(nèi)部delete釋放動(dòng)態(tài)申請(qǐng)的空間,然而我們又delete了一次,,等于釋放了兩次空間,。
*** Error in `./a.out': double free or corruption
(out): 0x00007fff21931150 ***
書上的一個(gè)例子,有點(diǎn)問題記錄下
- #include <memory>
- #include <iostream>
-
- using namespace std;
-
- int main()
- {
- shared_ptr<int>p(new int(42));
- int *q = p.get();
- cout << "count:" << p.use_count() << endl;
- //delete q; error:
- {
- auto t = shared_ptr<int>(q); //轉(zhuǎn)換
- cout << "count:" << t.use_count() << endl;
- }
- int foo = *p;
- cout << foo << endl;
- }
書上沒有auto t,,按照書上foo可以正常使用,,估計(jì)是編譯器優(yōu)化。
書上意思是智能指針t也指向q所指向的內(nèi)存,,但是引用計(jì)數(shù)都是1,,函數(shù)塊結(jié)束后,t被釋放,,內(nèi)存也被delete,,那么p指向的未定義了。
<<4.智能指針和異常
使用智能指針,,即使程序塊過早結(jié)束,,智能指針類也能確保內(nèi)存不再需要時(shí)將其釋放。
但是普通指針就不會(huì)
- void fun( )
- {
- int *p = new int(42);
- //如果這時(shí)拋出一個(gè)異常且未捕獲,,內(nèi)粗不會(huì)被釋放,,但是智能指針就可以釋放。
- delete p;
- }
<<5.智能指針和啞類
標(biāo)準(zhǔn)很多都定義了析構(gòu)函數(shù),,負(fù)責(zé)清理對(duì)象使用的資源,,但是一些同時(shí)滿足c和c++的設(shè)計(jì)的類,通常都要求我們自己來釋放資源,。
通過智能指針可以很好的解決這個(gè)問題
舉個(gè)網(wǎng)絡(luò)連接的例子:
- connection connect(*destination);
- void disconnect(connect);
- void f(destination &d)
- {
- connection c = connect(&d);
- disconnect(d);//如果沒有調(diào)用disconnect,,那么永遠(yuǎn)不會(huì)斷開連接,。
- }
- //使用智能指針優(yōu)化,等于自己定義了delete代替本身的delete
- connection connect(*destination);
- void end_disconnect(connection*p) {disconnect(p);}
- void f(destination &d)
- {
- connection c = connect(&d);
- shared_ptr<connection>p(&d, end_connect);
- //f退出時(shí),,會(huì)自動(dòng)調(diào)用end_connect,。
- }
demo,用string代替connection類型。
- #include <iostream>
- #include <string>
- #include <memory>
-
- using namespace std;
- typedef string connection;
-
- connection& connect(connection *s)
- {
- cout << "正在連接..." << endl;
- s = new connection("connect");
- return *s;
- }
-
- void disconnect(connection *s)
- {
- cout << "正在斷開連接..." << endl;
- }
-
- int main()
- {
- connection p;
- connection *d;
- p = connect(d);
- shared_ptr<connection>sp(&p,disconnect);//&p
- }
這樣做即使我們忘記寫斷開連接或者中間發(fā)生了異常都會(huì)保證執(zhí)行斷開連接的代碼,。
!注意:智能指針陷阱
*不使用相同的內(nèi)置指針值初始化(或reset)多個(gè)智能指針 //多個(gè)智能指針還是單獨(dú)的指向內(nèi)置指針的內(nèi)存,,use_count分別為1
*不delete get( )返回的指針 //兩次delete釋放,智能指針內(nèi)部也會(huì)delete
*不使用get( )初始化或reset另一個(gè)智能指針
//free( ): invalid pointer:也是多次釋放
*如果你使用get( )返回的指針,,記住當(dāng)最后一個(gè)對(duì)應(yīng)的智能指針銷毀后,你的指針就變得無效了
*如果你使用智能指針管理的資源不是new分配的內(nèi)存,,記住傳遞給它一個(gè)刪除器(刪除函數(shù)向上面的disconnect( )),。
課后題12.15
使用lambda改寫connect函數(shù)。
- #include <iostream>
- #include <string>
- #include <memory>
- #include <functional>
- #include <algorithm>
-
- using namespace std;
- typedef string connection;
-
- connection& connect(connection *s)
- {
- cout << "正在連接..." << endl;
- s = new connection("connect");
- return *s;
- }
-
- void disconnect(connection *s)
- {
- cout << "正在斷開連接..." << endl;
- }
-
- int main()
- {
- connection p;
- connection *d;
- p = connect(d);
- //shared_ptr<connection>sp(&p,disconnect);
- //error:lambda代表了刪除函數(shù),。那么參數(shù)列表也要和刪除函數(shù)一致,,因?yàn)閐elete內(nèi)部是free(p)。
- //shared_ptr<connection>sp(&p, [&p] { disconnect(&p); });
- shared_ptr<connection>sp(&p, [](connection *s) { disconnect(s); });
- }
shared_ptr 的傳遞刪除器(deleter)方式比較簡(jiǎn)單, 只需要在參數(shù)中添加具體的刪除器函數(shù)名, 即可; 注意是單參數(shù)函數(shù);
unique_ptr 的刪除器是函數(shù)模板(function template), 所以需要在模板類型傳遞刪除器的類型(即函數(shù)指針(function pointer)), 再在參數(shù)中添加具體刪除器;
2.unique_ptr
介紹:一個(gè)unique_ptr 擁有它所指向的對(duì)象,,和shared_ptr不同,,某個(gè)時(shí)刻只能有一個(gè)unique_ptr 指向一個(gè)給定對(duì)象,當(dāng)unique_ptr 被銷毀時(shí),,對(duì)象也被銷毀
<1.
unique沒有類似make_shared,,必須手動(dòng)new,將其綁定
由于unique_ptr獨(dú)占它所指向的對(duì)象,因此他不支持普通的拷貝和賦值
但是有種特殊的拷貝可以支持:我們可以拷貝或賦值一個(gè)即將要被銷毀的unique_ptr,。
- #include <memory>
- #include <iostream>
- #include <string>
-
- using namespace std;
-
- unique_ptr<int> clone(int p)
- {
- unique_ptr<int>q(new int(p));
- return q;
- //return unique<int>q(new int(p));
- }
-
- int main()
- {
- unique_ptr<string>p(new string("aaa"));
- shared_ptr<string>p2(new string("aaa"));
- //unique_ptr<string>p3(p); error:不能拷貝
- //unique_ptr<string>p4 = p; error:不能賦值
- unique_ptr<string>p5;
- string s = "a";
- //p5.reset(&s); error:兩次釋放
-
- //兩種轉(zhuǎn)移所有權(quán)的方法
- unique_ptr<string>p6(p.release());//p.release(),,釋放p對(duì)指針對(duì)象的控制權(quán),返回指針并將p置空,并不會(huì)釋放內(nèi)存
- unique_ptr<string>p7;
- p7.reset(p6.release()); //p6釋放控制權(quán),,p7指向這個(gè)對(duì)象,。
-
- //特殊的拷貝和賦值
- int i = 10;
- clone(i);
- }
在早的版本中提供了auto_ptr的類,它有unique_ptr 的部分特性,,但是不能在容器中保存auto_ptr, 也不能在函數(shù)中返回 auto_ptr, 編寫程序時(shí)應(yīng)該使用unique_ptr.
<2.向unique_ptr 傳遞刪除器
類似shared_ptr, unique_ptr 默認(rèn)情況下用delete釋放它指向的對(duì)象,,和shared_ptr 一樣我們可以重載一個(gè)unique_ptr 中默認(rèn)的刪除器類型。
重載一個(gè)unique_ptr 中的刪除器會(huì)影響到unique_ptr 類型及如何構(gòu)造該類型的對(duì)象,,
我們必須在尖括號(hào)中unique_ptr 指向類型之后提供刪除器的類型,,在創(chuàng)建或reset一個(gè)這種unique_ptr類型的對(duì)象時(shí),必須提供一個(gè)指定類型的可調(diào)用對(duì)象(刪除器)
- #include <memory>
- #include <iostream>
-
- using namespace std;
-
- typedef int connection;
-
- connection* connect(connection *d)
- {
- cout << "正在連接..." << endl;
- d = new connection(40);
- return d;
- }
-
- void disconnect(connection *p)
- {
- cout << "斷開連接..." << endl;
- }
-
- int main()
- {
- connection *p,*p2;
- p2 = connect(p);
- cout << p << endl;
- cout << *p2 << endl;
- unique_ptr<connection, decltype(disconnect)*>q(p2,disconnect);
- //在尖括號(hào)中提供類型,,圓括號(hào)內(nèi)提供尖括號(hào)中的類型的對(duì)象,。
- //使用decltype()關(guān)鍵字返回一個(gè)函數(shù)類型,所以必須添加一個(gè)*號(hào)來指出我們使用的是一個(gè)指針
- }
注意:
p.release( ); //error:p2不會(huì)釋放內(nèi)存,,而且丟失了指針
auto q = p.release( ); //q 是int * 類型,, 記得delete釋放q
c++11
3.weak_ptr
weak_ptr 是一種不控制對(duì)象生存期的智能指針,,它指向由一個(gè)shared_ptr 管理的對(duì)象。
- #include <memory>
- #include <iostream>
-
- using namespace std;
-
- int main()
- {
- shared_ptr<int>p0 = make_shared<int>(42);
- auto p1 = p0;
- cout << "p1 count:" << p1.use_count() << endl;
- weak_ptr<int>p2;
- weak_ptr<int>p3(p1); //與p1指向相同的對(duì)象
- p3 = p0; //與p0指向相同的對(duì)象
- p2 = p3;
- cout << "p2 count:" << p2.use_count() << endl;
- //w.reset() 將w置空
- p2.reset(); //將p2置空
- cout << "p2 count:" << p2.use_count() << endl;
- cout << "p3 count:" << p3.use_count() << endl;
- //w.expired() 若w.use_count()為0,返回true,否則返回false expired(過期的)
- //w.expried() 也就是判斷shared_ptr的count為0嗎,。
- cout << "p2 expired:" << p2.expired() << endl;
- cout << "p3 expired:" << p3.expired() << endl;
- //w.lock() 如果expired為true,返回一個(gè)空的shared_ptr,否則返回一個(gè)指向w的對(duì)象的shared_ptr
- //w.lock() 也就是如果shared_ptr的count為0,,返回空,不為0,,返回shared_ptr,。
- auto p4 = p2.lock();
- cout << "p4 count:" << p4.use_count() << endl;
- auto p5 = p3.lock();//引用計(jì)數(shù)加1
- cout << "p5 count:" << p5.use_count() << endl;
- }
!注意:
當(dāng)我們創(chuàng)建一個(gè)weak_ptr 必須用一個(gè) shared_ptr 初始化,。
引入lock和expired是防止在weak_ptr 不知情的情況下,,shared_ptr 被釋放掉
weak_ptr 不會(huì)更改shared_ptr 的引用計(jì)數(shù)。
例子:
定義一個(gè)StrBlobPtr(內(nèi)部weak_ptr)類打印StrBlob中的元素
上面的StrBlobPtr 例子的改進(jìn),。
StrBlobPtr內(nèi)部是weak_ptr實(shí)現(xiàn)的,,它實(shí)際上就是一個(gè)助手類,作用就是類似一個(gè)旁觀者,,一直觀測(cè)StrBlob的資源使用情況
- /*
- *避免拷貝,多個(gè)指針共用一個(gè)vector<string>
- *使用weak_ptr訪問共享的對(duì)象
- * */
-
- #include <iostream>
- #include <vector>
- #include <string>
- #include <initializer_list>
- #include <memory>
- #include <stdexcept>
- #include <fstream>
- #include <sstream>
-
- class StrBlob;
- class StrBlobPtr;
-
-
- class StrBlob
- {
- public:
- friend class StrBlobPtr;
- typedef std::vector<std::string>::size_type size_type;
- StrBlob(); //默認(rèn)構(gòu)造函數(shù)
- StrBlob(std::initializer_list<std::string>il); //拷貝構(gòu)造函數(shù)
- size_type size() { return data->size(); } //對(duì)data進(jìn)行解引用就是對(duì)vector<string>操作
- std::string& front();
- std::string& back();
- const std::string& front()const;
- const std::string& back()const;
- void push_back(const std::string &s) { data->push_back(s); }
- void pop_back();
- //StrBlobPtr begin() { return StrBlobPtr(*this); }
- //StrBlobPtr end() { auto ret = StrBlobPtr(*this, data->size());
- // return ret; }
-
- private:
- void check(size_type sz, std::string msg)const;
- std::shared_ptr<std::vector<std::string>>data;
- };
-
- std::string& StrBlob::front()
- {
- const auto &s = static_cast<const StrBlob*>(this)->front();
- return const_cast<std::string&>(s);
- }
-
- std::string& StrBlob::back()
- {
- const auto &s = static_cast<const StrBlob*>(this)->back();
- return const_cast<std::string&>(s);
- }
-
- const std::string& StrBlob::front()const
- {
- check(0, "front on empty vector");
- return data->front();
- }
-
- const std::string& StrBlob::back()const
- {
- check(0, "back on empty vector");
- return data->back();
- }
-
- void StrBlob::check(size_type sz, std::string msg)const
- {
- if(sz >= data->size())
- throw std::out_of_range(msg);
- }
-
- StrBlob::StrBlob():
- data(std::make_shared<std::vector<std::string>>()) { }
-
- StrBlob::StrBlob(std::initializer_list<std::string> il):
- data(std::make_shared<std::vector<std::string>>(il)) { }
-
- /* --------------------------------------------------------------------------------- */
-
- //必須定義在StrBlobPtr的后面
- //否則error: invalid use of incomplete type ‘class StrBlob’
- class StrBlobPtr
- {
- public:
- friend StrBlob;
- StrBlobPtr():curr(0){ }
- StrBlobPtr(StrBlob &s, std::size_t sz = 0):
- wptr(s.data), curr(sz){ }
- std::string& deref()const;//返回當(dāng)前string
- StrBlobPtr& incr(); //遞增
-
- private:
- std::shared_ptr<std::vector<std::string>> check(std::size_t i, const std::string &msg)const;
- std::weak_ptr<std::vector<std::string>> wptr;
- std::size_t curr; //當(dāng)前下標(biāo)
- };
-
- StrBlobPtr& StrBlobPtr::incr()
- {
- check(curr, "increment past end of StrBlobPtr");
- ++curr; //推進(jìn)當(dāng)前位置,。
- return *this; //為什么要return *this, 如果再次自加可以重復(fù),舉個(gè)例子就像賦值一樣 a = b = c; 如果不返回對(duì)象不能繼續(xù)賦值,。
- } //return *this是一份拷貝,。 return this是地址。
-
- std::string& StrBlobPtr::deref()const
- {
- auto p = check(curr, "dereference past end"); //shared_ptr引用計(jì)數(shù)會(huì)增加,但是作用域結(jié)束后,,引用計(jì)數(shù)又會(huì)減1
- return (*p)[curr]; //p是所指的vector
- }
-
- //check檢查是否存在shared_ptr和大小
- std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::string &msg)const
- {
- auto ret = wptr.lock(); //檢查是否存在,,存在返回shared_ptr,不存在返回空的shared_ptr.
- if(!ret)
- throw std::runtime_error("unbound StrBlobPtr");
- if(i >= ret->size())
- throw std::out_of_range(msg);
- return ret;
- }
-
-
- int main(int argc, char*argv[])
- {
- std::fstream is(argv[1]);
- std::string s;
- StrBlob S;
- while(std::getline(is, s))
- {
- std::string temp;
- std::istringstream ist(s);
- while(!ist.eof())
- {
- ist >> temp;
- S.push_back(temp);
- }
- }
-
- std::cout << "size:" << S.size() << std::endl;
-
- StrBlobPtr sp(S);
- for(auto i = 0; i < S.size(); ++i)
- {
- std::cout << sp.deref() << std::endl;
- sp.incr();
- }
- }
4.動(dòng)態(tài)數(shù)組
new和delete一次只能分配和釋放一個(gè)對(duì)象,但有時(shí)我們需要一次為很多對(duì)象分配內(nèi)存的功能
C++和標(biāo)準(zhǔn)庫引入了兩種方法,,另一種new 和 allocator,。
使用allocator 通常會(huì)提供更好的性能和更靈活的內(nèi)存管理能力。
<1.new和數(shù)組
動(dòng)態(tài)數(shù)組不是數(shù)組類型,。
- #include <iostream>
- #include <memory>
-
- using namespace std;
-
- typedef int arr[10];
-
- int main()
- {
- int *p = new int[10];
- int *p2 = new arr;
- for(int i = 0; i < 10; i++)
- {
- p[i] = i;
- }
- for(int i = 0; i < 10; i++)
- cout << p[i] << " ";
- cout << endl;
- //for(const int i : p); //error:動(dòng)態(tài)分配數(shù)組返回的不是數(shù)組類型,,而是數(shù)組元素的指針。所以不能用范圍for
-
- /*---------------------------------- */
- //初始化動(dòng)態(tài)數(shù)組
- int *pi = new int[10]; //未初始化
- int *pi2 = new int[10](); //初始化為0,且有括號(hào)必須為空
- string *ps = new string[10]; //10個(gè)空string
- string *ps2 = new string[10](); //10個(gè)空string
- //可以使用列表初始化
- int *pi3 = new int[10]{1,2,3,4,5,6,7,8,9,0};//初始值列表里的值不能多于容量,,否則new失敗,,不會(huì)分配內(nèi)存
- string *ps3 = new string[10]{"a","b","c","d","e","f","g","h","i",string(3,'x')};
- //釋放動(dòng)態(tài)數(shù)組
- delete []pi3; //必須要加[]括號(hào),且釋放動(dòng)態(tài)數(shù)字時(shí)是逆序釋放,。如果delete動(dòng)態(tài)數(shù)組不加[],行為是未定義的,。
-
- /*----------------------------------- */
- //智能指針和動(dòng)態(tài)數(shù)組,標(biāo)準(zhǔn)庫定義了特別的unique_ptr來管理,當(dāng)uo銷毀它管理的指針時(shí),會(huì)自動(dòng)調(diào)用delete [];
- int *p5 = new int[10];
- //unique_ptr<int[]> up;
- unique_ptr<int[]> up(p5);
- for(int i = 0; i < 10; ++i)
- cout << up[i] << " ";
- cout << endl;
- //如果使用shared_ptr的話我們必須自己定義delete函數(shù)
- shared_ptr<int>sp(new int[10], [](int *p) { delete []p;});
- //智能指針不支持算數(shù)類型,,如果要訪問數(shù)組中的元素,,必須使用get函數(shù)返回一個(gè)內(nèi)置指針。
- cout << (*sp.get()) << endl;
-
- }
課后題12.24
- #include <iostream>
- #include <string>
-
- using namespace std;
-
- int main()
- {
- string s1;
- cin >> s1;
- string *p = new string(s1);
- const char *p1 = new char[s1.size()];
- p1= (*p).c_str(); //轉(zhuǎn)換為一個(gè)c風(fēng)格的字符串,,但是是const類型的
- cout << "*p1 " << *p1 << endl; //只會(huì)輸出第一個(gè)字母,,說明new創(chuàng)建返回的不是數(shù)組類型的指針,,而是元素類型
- cout << "p1 ";
- for(int i = 0; i < s1.size(); ++i)
- cout << p1[i] << " ";
- cout << endl;
- const char *p2 = new char[10];
- string ss;
- ss = "aaaaaaaaaaaaaaaaaaaaaa"; //超出了動(dòng)態(tài)分配的內(nèi)存空間
- p2 = ss.c_str();
- cout << "p2 ";
- for(int i = 0; i < ss.size(); ++i) //結(jié)果還是正常輸出了!
- cout << p2[i] << " ";
- cout << endl;
- }
<2.使用allocator類
引入allocator的原因是new類上的缺陷
new它將內(nèi)存分配和對(duì)象構(gòu)造結(jié)合到了一起
比如:string *p = new string;
new是現(xiàn)在找一塊內(nèi)存分配,,不夠繼續(xù)malloc,,在分配內(nèi)存的地址上調(diào)用構(gòu)造函數(shù),delete也一樣,,在釋放內(nèi)存的時(shí)候也會(huì)調(diào)用析構(gòu)函數(shù),。
內(nèi)置類型要指定初值。
但是如果我們希望指定它的初值,,不讓它調(diào)用默認(rèn)構(gòu)造函數(shù)new就不可行了,,而且本身調(diào)用了一次構(gòu)造函數(shù),然后我們賦值了一次,。
更重要的是,沒有默認(rèn)構(gòu)造函數(shù)的就不能動(dòng)態(tài)分配內(nèi)存了,。
- #include <iostream>
- #include <memory>
- #include <string>
-
- using namespace std;
-
- int main()
- {
- //allocator<T>a; 定義了一個(gè)名為a的allocator對(duì)象,,可以為T對(duì)象分配空間
- allocator<string>alloc;
- //a.allocate(n); 為T分配n個(gè)空間
- string *const p = alloc.allocate(10); //為10個(gè)string分配了內(nèi)存,且內(nèi)存是原始的,未構(gòu)造的
- cout << sizeof(p) << endl;
- //釋放p中地址的內(nèi)存,,這快內(nèi)存保存了n個(gè)T對(duì)象,,p必須是allocte返回的指針,n必須是allocate(n)的n
- //且在調(diào)用deallocate時(shí)必須先毀壞這塊內(nèi)存中創(chuàng)建的對(duì)象
- alloc.deallocate(p,10);
- //p是allocate返回的指針,,construction是傳遞給類型T的構(gòu)造函數(shù),,在p指向的內(nèi)存中構(gòu)造一個(gè)對(duì)象
- //alloc.construct(p, construction)
- //對(duì)p指向的對(duì)象進(jìn)行析構(gòu)函數(shù)。
- //alloc.destroy(p);
-
- allocator<string>alloc2;
- auto const p2 = alloc2.allocate(10);
- auto q = p2;
- auto q2 = p2;
- //為了使用我們allocate的內(nèi)存,,必須用construct構(gòu)造對(duì)象,,使用未定義的內(nèi)存,其行為是未定義的,。
- alloc2.construct(q++, "sssss");
- cout << *q2++ << endl;
- alloc2.construct(q++, "he");
- cout << *q2++ << endl;
- alloc2.construct(q++, 10, 'x');
- cout << *q2 << endl;
- //對(duì)使用過的內(nèi)存進(jìn)行釋放,,調(diào)用string的析構(gòu)函數(shù),注意不能destory未使用的內(nèi)存,。
- while(q2 != p2)
- alloc2.destroy(q2--);
- //元素被銷毀后,,我們可以重新使用這塊內(nèi)存,也可以歸還給系統(tǒng)
- alloc2.deallocate(p2, 10);
- //deallocate的指針不能為空,必須指向allocate分配的內(nèi)存,且deallocate和allocate的大小相同,。
-
- }
標(biāo)準(zhǔn)庫還為allocator定義了兩個(gè)伴隨算法
在未初始化的內(nèi)存中創(chuàng)建對(duì)象,,都定義在頭文件memory
- uninitialized_copy(b,e,b2) b,2是輸入容器的迭代器,b2是內(nèi)存的起始地址,,要保證空間足夠
- uninitialized_copy_n(b,n,b2) b是輸入容器的起始迭代器,,復(fù)制n個(gè),復(fù)制到以b2為起始地址的動(dòng)態(tài)內(nèi)存中
- uninitialized_fill(b,e,t) b,e是動(dòng)態(tài)內(nèi)存的起始和終止位置,,t是要fill的元素
- uninitialized_fill_n(b,n,t) b是動(dòng)態(tài)內(nèi)存的起始,,fill n個(gè),,t是要fill的元素
- #include <iostream>
- #include <string>
- #include <vector>
- #include <memory>
-
- using namespace std;
-
- int main()
- {
- //copy返回的是最后一個(gè)元素的下一個(gè)位置,fill返回void
- vector<string>ivec(10,"a");
- allocator<string>alloc;
- auto const p = alloc.allocate(ivec.size()*4);
- auto q = uninitialized_copy(ivec.begin(),ivec.end(), p);
- auto q2 = q;
- while(q-- != p)
- cout << *q << " ";
- cout << endl;
- uninitialized_fill_n(q2, ivec.size(), "b");
- for(auto i = 0; i < ivec.size(); ++i)
- cout << *q2++ << " ";
- cout << endl;
-
- vector<string>ivec2(10,"c");
- auto q3 = uninitialized_copy_n(ivec2.begin(),10,q2);
- for(auto i = 0; i < ivec2.size(); ++i)
- cout << *q2++ << " ";
- cout << endl;
- uninitialized_fill(q3,q3+10, "d");
- for(auto i = 0; i < ivec2.size(); ++i)
- cout << *q3++ << " ";
- cout << endl;
-
- }
版權(quán)聲明:free, open, share
|