----------------------------------------shared_ptr---------------------------------------
引子 c++中動(dòng)態(tài)內(nèi)存的管理是通過(guò)new和delete來(lái)完成的,只要保證new和delete的配對(duì)使用,是沒(méi)有問(wèn)題的。但是有時(shí)候我們會(huì)忘記釋放內(nèi)存,甚至有時(shí)候我們根本就不知道什么時(shí)候釋放內(nèi)存。特別時(shí)在多個(gè)線程間共享數(shù)據(jù)時(shí),更難判斷內(nèi)存該何使釋放。這種情況下就機(jī)器容易產(chǎn)生引用非法內(nèi)存的指針?! ?/span> 為了更容易(同時(shí)也更安全的管)的使用動(dòng)態(tài)內(nèi)存,新的標(biāo)準(zhǔn)庫(kù)(C++11)提供了兩種智能指針(smart pointer)類型來(lái)管理動(dòng)態(tài)對(duì)象。智能指針的行為類似于常規(guī)指針。重要的區(qū)別是它負(fù)責(zé)自動(dòng)釋放所指向的對(duì)象。新標(biāo)準(zhǔn)提供的這兩種智能指針的區(qū)別在于管理底層指針的方式:shared_ptr允許多個(gè)指針指向同一個(gè)對(duì)象;unique_ptr則獨(dú)占所指向的對(duì)象。標(biāo)準(zhǔn)庫(kù)還定義了一個(gè)weak_ptr的伴隨類,他是一種弱引用,指向shared_ptr所管理的對(duì)象。這三種類型都定義在memory頭文件中。
初始化 sahred_ptr 智能指針的使用方式與普通指針類似。解引用一個(gè)智能指針?lè)祷厮赶虻膶?duì)象。如果在一個(gè)條件判斷中使用智能指針,效果就是檢測(cè)它是否為空:
#include <iostream>
using namespace std;
int main()
{
if(!p1) //!默認(rèn)初始化的智能指針中保存著一個(gè)空指針!并不是""空字符串
cout<<"p1==NULL"<<endl;
shared_ptr<string> p2(new string);
// shared_ptr<int> pa = new int(1);//!error:不允許以暴露裸漏的指針進(jìn)行賦值操作。
}
關(guān)于其它初始化智能指針的方法,如下;不推薦!在這里展示出來(lái)是希望極力避免不安全的使用范例。 /*不推薦*/ int * p = new int(32); shared_ptr<int> pp(p); cout<<*pp<<endl; /*意外的情況*/ // delete p; //!不小心把delete掉了。 // cout<<*pp<<endl;· //!pp也不再有效。
關(guān)于get()函數(shù); 智能指針定義了一個(gè)名為get的函數(shù),它返回一個(gè)內(nèi)置指針,指向智能指針的管理的對(duì)象。此函數(shù)設(shè)置的初衷是當(dāng)我們向不能使用智能指針的代碼傳遞一個(gè)內(nèi)置指針。使用get返回指針的代碼不能delete此指針。 #include <iostream> #include <memory> using namespace std; void useShared_ptr(int *p) { cout<<*p<<endl; } void delePointer(int *p) { delete p; } int main(int argc, char *argv[]) { shared_ptr<int> p1 = make_shared<int>(32); // shared_ptr<int>p2(p1.get()); //!錯(cuò)誤的用法:但是p1、p2各自保留了對(duì)一段內(nèi)存的引用計(jì)數(shù),其中有一個(gè)引用計(jì)數(shù)耗盡,資源也就釋放了。 useShared_ptr(p1.get()); // delePointer(p1.get()); //!error: return 0; } 再次聲明:get用來(lái)將指針的訪問(wèn)權(quán)限傳遞給代碼,只有在確定代碼不會(huì)delete指針的情況下,才能使用get。特別是,永遠(yuǎn)不要用get初始化另一個(gè)智能指針或者為另一個(gè)智能指針賦值!
關(guān)于mak_shared函數(shù): 最安全的分配和使用動(dòng)態(tài)內(nèi)存的方法是調(diào)用一個(gè)名為make_shared的標(biāo)準(zhǔn)庫(kù)函數(shù),此函數(shù)在動(dòng)態(tài)內(nèi)存中分配一個(gè)對(duì)象并初始化它,返回此對(duì)象的shared_ptr。與只能指針一樣,make_shared也定義在頭文件memory中。
#include <iostream>
using namespace std;
int main()
{
shared_ptr<int> p3 = make_shared<int>(42);
cout<<*p3<<endl;
shared_ptr<string> pstr = make_shared<string>("99999");
cout<<*pstr<<endl;
shared_ptr<int> pint = make_shared<int>(); //!默認(rèn)初始化為 0
cout<<*pint<<endl;
auto pau = make_shared<string>("auto"); //!更簡(jiǎn)單,更常用的方式。
cout<<*pau<<endl;
}
使用make_shared用其參數(shù)來(lái)構(gòu)造給定類型的對(duì)象;傳遞的參數(shù)必須能夠與該類型的某個(gè)構(gòu)造函數(shù)相匹配。 通常我們用auto來(lái)定義一個(gè)對(duì)象來(lái)保存make_shared的結(jié)果,這種方式更為簡(jiǎn)單。
shared_ptr的拷貝和賦值 當(dāng)進(jìn)行拷貝或者賦值操作時(shí),每個(gè)shared_ptr都會(huì)記錄有多少個(gè)其他的shared_ptr指向相同的對(duì)象: #include <iostream>
using namespace std;
int main()
{
auto p = make_shared<int>(42); //!p指向的對(duì)象只有p一個(gè)引用者。
cout<<p.use_count()<<endl;
auto q(p); //!p和q指向相同的對(duì)象,此對(duì)象有兩個(gè)引用者。
cout<<p.use_count()<<endl;
return 0;
}
shared_ptr作返回值: #include <iostream>
using namespace std; shared_ptr<string> factory(const char* p){ return make_shared<string>(p); } void use_factory(){ shared_ptr<string> p = factory("helloworld"); cout<<*p<<endl; //!離開(kāi)作用域時(shí),p引用的對(duì)象被銷毀。 shared_ptr<string> return_share_ptr()
引用計(jì)數(shù): 可以認(rèn)為每個(gè)shared_ptr都有一個(gè)關(guān)聯(lián)的計(jì)數(shù)器,通常稱其為引用計(jì)數(shù)。無(wú)論何時(shí)我們拷貝一個(gè)shared_ptr,計(jì)數(shù)器都會(huì)遞增。例如,當(dāng)用一個(gè)shared_ptr去初始化另一個(gè)shared_ptr;當(dāng)我們給shared_ptr賦予一個(gè)新的值或者是shared_ptr被銷毀(例如一個(gè)局部的shared_ptr離開(kāi)其作用域)時(shí),計(jì)數(shù)器就會(huì)遞減。一旦一個(gè)shared_ptr的計(jì)數(shù)器變?yōu)?,他就會(huì)自動(dòng)釋放自己所管理的對(duì)象。 #include <iostream>
其他shared_ptr操作 shared_ptr還定義了一些其他的操作,參考前面的shared_ptr操作表格,例如,我們可以用reset將一個(gè) 新的指針賦予一個(gè)shared_ptr:
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<string> p1(new string("helloworld--1"));
// p1 = new string("helloworld2--2");//error!
p1.reset(new string("helloworld2--2"));
cout<<*p1<<endl;
}
與賦值類似,reset會(huì)更新(-1)引用計(jì)數(shù),如果需要的話,會(huì)釋放p1指向的對(duì)象。reset成員經(jīng)常與unique一起使用,來(lái)控制多個(gè)shared_ptr的共享對(duì)象。在改變底層對(duì)象之前,我們?cè)跈z查自己是否是當(dāng)前對(duì)象僅有的用戶。如果不是,在改變之前要做一份新的拷貝: #include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<string> p1(new string("helloworld--1"));
shared_ptr<string> p2(p1);
if(p1.unique())
cout<<*p1 + string(" is unique!")<<endl;
else{
p1.reset(new string("new reset!"));
cout<<*p1<<endl;
}
}
容器中的shared_ptr-記得用erease節(jié)省內(nèi)存 對(duì)于一塊內(nèi)存,shared_ptr類保證只要有任何shared_ptr對(duì)象引用它,他就不會(huì)被釋放掉。由于這個(gè)特性,保證shared_ptr在不用之后不再保留就非常重要了,通常這個(gè)過(guò)程能夠自動(dòng)執(zhí)行而不需要人工干預(yù),有一種例外就是我們將shared_ptr放在了容器中。所以永遠(yuǎn)不要忘記erease不用的shared_ptr。 #include <iostream>
using namespace std;
int main()
{
list<shared_ptr<string>>pstrList;
pstrList.push_back(make_shared<string>("1111"));
pstrList.push_back(make_shared<string>("2222"));
pstrList.push_back(make_shared<string>("3333"));
pstrList.push_back(make_shared<string>("4444"));
for(auto p:pstrList)
{
if(*p == "3333");
{
/*do some thing!*/
}
cout<<*p<<endl;
}
/*包含"3333"的數(shù)據(jù)我們已經(jīng)使用完了!*/
list<shared_ptr<string>>::iterator itr = pstrList.begin();
for(;itr!=pstrList.end();++itr)
{
if(**itr == "3333"){
cout<<**itr<<endl;
pstrList.erase(itr);
}
}
cout<<"-------------after remove------------"<<endl;
for(auto p:pstrList)
{
cout<<*p<<endl;
}
}
狀態(tài)共享——why use shared_ptr? 使用shared_ptr在一個(gè)常見(jiàn)的原因是允許多個(gè)多個(gè)對(duì)象共享相同的狀態(tài),而非多個(gè)對(duì)象獨(dú)立的拷貝! #include <iostream>
using namespace std;
void copyCase()
{
list<string> v1({"1","b","d"});
list<string> v2 = v1; //!v1==v2占用兩段內(nèi)存
v1.push_back("cc"); //!v1!=v2
for(auto &p:v1){
cout<<p<<endl;
}
cout<<"--------void copyCase()---------"<<endl;
for(auto &p:v2){
cout<<p<<endl;
}
} //v1和v2分屬兩個(gè)不同的對(duì)象,一個(gè)改變不會(huì)影響的狀態(tài)。
void shareCase()
{
shared_ptr<list<string>> v1 = make_shared<list<string>>(2,"bb");
shared_ptr<list<string>> v2 = v1;
(*v1).push_back("c2c");
for(auto &p:*v1){
cout<<p<<endl;
}
cout<<"----------shareCase()--------"<<endl;
for(auto &p:*v2){
cout<<p<<endl;
}
} //v1和v2屬于一個(gè)對(duì)象的兩個(gè)引用,有引用計(jì)數(shù)為證,其內(nèi)容的改變是統(tǒng)一的。
int main()
{
copyCase();
cout<<"++++++++++++++++"<<endl;
shareCase();
}
智能指針與異常
異常發(fā)生后,常規(guī)的動(dòng)態(tài)內(nèi)存常常不能正確釋放。但是如果使用智能指針,即程序過(guò)早結(jié)束,智能指針也能確保在內(nèi)存不需要時(shí)將其釋放:
void f()
{
shared_ptr<int>sp(new int(42)) ;
}
函數(shù)的推出,要么有兩種情況,正常處理結(jié)束或者發(fā)生了異常,無(wú)論哪種情況,局部對(duì)象都會(huì)被銷毀。在上面的程序中,sp是一個(gè)shared_ptr,因此sp銷毀時(shí)會(huì)檢查引用計(jì)數(shù)。在此例中,sp是指向這塊內(nèi)存的唯一指針。所以會(huì)被釋放掉。
與之相對(duì)的,當(dāng)發(fā)生異常時(shí),我們直接管理的內(nèi)存時(shí)不會(huì)自動(dòng)釋放的,如果使用內(nèi)置指針管理內(nèi)存,且在new之后對(duì)應(yīng)的delet之前發(fā)生異常,則內(nèi)存不會(huì)釋放。
void f()
{
int *p = new int(42);
//code//!異常拋出,且沒(méi)有在f()中被捕獲。
delete p;
}
如果在new和delete之間發(fā)生異常,且異常未在f()中捕獲,則內(nèi)存就永遠(yuǎn)不會(huì)被釋放了。
shared_ptr對(duì)象的銷毀 1)管理動(dòng)態(tài)數(shù)組 默認(rèn)情況下,shared_ptr指向的動(dòng)態(tài)的內(nèi)存是使用delete來(lái)刪除的。這和我們手動(dòng)去調(diào)用delete然后調(diào)用對(duì)象內(nèi)部的析構(gòu)函數(shù)是一樣的。與unique_ptr不同,shared_ptr不直接管理動(dòng)態(tài)數(shù)組。如果希望使用shared_ptr管理一個(gè)動(dòng)態(tài)數(shù)組,必須提供自定義的刪除器來(lái)替代delete 。
#include <iostream>
通過(guò)自定義刪除器的方式shared_ptr雖然管理的是一個(gè)動(dòng)態(tài)數(shù)組。但是shard_ptr并不支持下標(biāo)運(yùn)算符的操作。而且智能指針類型不支持指針?biāo)阈g(shù)運(yùn)算(不能取地址)。因此為了訪問(wèn)數(shù)組中的元素,必須用get獲取一個(gè)內(nèi)置指針,然后用它來(lái)訪問(wèn)數(shù)組元素。
2)管理非常規(guī)動(dòng)態(tài)對(duì)象 某些情況下,有些動(dòng)態(tài)內(nèi)存也不是我們new出來(lái)的,如果要用shared_ptr管理這種動(dòng)態(tài)內(nèi)存,也要自定義刪除器。 #include <iostream>
在這里可以設(shè)想一下TCP/IP中鏈接的打開(kāi)和關(guān)閉的情況,同理都可以使用智能指針來(lái)管理。
總結(jié): 最后總結(jié)一下上面所陳述的內(nèi)容,也是shared_ptr使用的基本規(guī)范 1)不使用相同的內(nèi)置指針值初始化(或reset)多個(gè)智能指針。 2)不delete get函數(shù)返回的指針。 3)如果你使用了get返回的指針,記住當(dāng)最后一個(gè)對(duì)應(yīng)的智能指針?shù)N毀后,你的指針就變?yōu)闊o(wú)效了。 4)如果你使用智能指針管理的資源不是new分配的內(nèi)存,記得傳遞給他一個(gè)刪除器。
weak_ptr weakptr使用的比較少,如有興趣了解,請(qǐng)去參考該篇文章:https://www.cnblogs.com/DswCnblog/p/5628314.html
|
|