前言
java
1.生命週期安全
全局對象:程序啓動時分配,程序結束時銷燬函數
局部自動對象:執行流進入其定義的塊時分配,執行流退出其定義的塊時銷燬spa
局部static對象:程序啓動時分配(但在其定義的塊或做用域內起做用),程序結束時銷燬指針
動態分配的對象:生命週期與建立地點無關,只有當顯示的被釋放時,纔會被銷燬對象
智能指針:標準庫定義,用於管理動態分配的內存,當一個對象應該被釋放時,指向它的智能指針能夠確保自動的釋放它生命週期
2.內存分類內存
靜態內存:用於存儲局部static對象、類的static數據成員、全局變量
ci
棧內存:用於存儲局部非static對象資源
堆內存(內存池):用於存儲動態分配的對象——動態分配的對象,其生命週期由程序控制,例如使用new或delete
3.動態內存
動態內存使用過程當中容易產生的問題
內存泄露:使用後忘記釋放內存
引用非法內存:在尚有指針引用內存的狀況下就釋放它
使用動態內存的緣由:
程序不知道本身須要使用多少對象
程序不知道所需對象的準確類型
程序須要在多個對象間共享底層數據——若兩個對象共享底層數據,當某個對象銷燬時,咱們不能單方面的銷燬底層數據
4.智能指針
智能指針和常規指針直接的區別:智能指針可以自動釋放所指向的內存(相似java中的垃圾回收機制),而常規指針不能。
智能指針默認初始化爲nullptr
智能指針分類
shared_ptr:容許多個指針指向同一對象
unique_ptr:獨佔所指向的對象
5.智能指針和異常
若是使用智能指針,即便程序非正常結束(異常),只要程序離開做用域,局部變量(智能指針)就會被銷燬,若此時引用計數爲0,則所指對象或內存也會被銷燬。
若是使用普通指針,則當程序非正常結束(異常)時,因爲未進行delete操做,所以指針所指的對象或內存未被釋放,形成內存泄露。
6.使用智能指針的基本規範(智能指針的陷阱)
不使用相同的內置指針值初始化(或reset)多個智能指針,不然容易屢次釋放同一對象或內存——由於這樣產生的智能指針是相互獨立的
int *p = new int(1);
shared_ptr<int> p1(p);
shared_ptr<int> p2(p); // 此時p1和p2相互獨立且引用計數都爲1,離開做用域時會釋放同一對象兩次
不delete``get()返回的指針,不然離開做用域時又要釋放一次內存
{
shared_ptr<int> p = make_shared<int>(1);
delete p.get(); // 釋放一次內存
} // 又要釋放一次內存
不使用get()初始化或reset另外一個智能指針,不然這兩個智能指針是獨立的,離開做用域時會釋放同一內存兩次
{
shared_ptr<int> p1 = make_shared<int>(1);
shared_ptr<int> p2(p1.get()); // 此時p1和p2相互獨立且引用計數都爲1
} // 釋放同一內存兩次
若是你使用get()返回的指針,記住當最後一個對於的智能指針銷燬後,你的指針就無效了——由於此時該指針爲懸空指針
int *p = nullptr;
{
shared_ptr<int> p1 = make_shared<int>(1);
p = p1.get();
} // 此時釋放p1所指向的內存,且p爲懸空指針
若是你使用智能指針管理的資源不是new分配的內存,記住傳遞給它一個刪除器(用於代替默認的delete)——由於智能指針的自動釋放默認使用delete,而delete只能用於動態內存或nullptr
智能指針
智能指針類所支持的操做
shared_ptr和unique_ptr都支持的操做
操做功能
shared_ptr<T> sp、unique_ptr<T> up空智能指針,能夠指向類型爲T的對象
*p解引用p所指的對象
p->data訪問對象*p中的data元素
p.get()返回p中保存的指針(值)
swap(p, q)、p.swap(q)交換p和q中保存的指針(值)
shared_ptr獨有的操做
操做功能
make_shared<T>(args)返回一個shared_ptr,指向一個動態分配的類型爲T的對象,使用args初始化此對象
shared_ptr<T> p(q)、p = q、shared_ptr<T> p(q, d)拷貝初始化,p和q指向同一對象,其中d是一個刪除器(用來代替默認的delete)
p.unique()若p.use_count() == 1,則返回true,不然返回false
p.use_count()返回與p共享對象的智能指針數量(引用計數)
p.reset()、p.reset(q)、p.reset(q, d)若p是惟一指向其對象的shared_ptr,則reset會釋放此對象。若傳遞了可選的參數(內置指針)q,則會令p指向q,不然會將p置爲空。若還傳遞了參數(可調用對象)d,則會調用d而不是delete來釋放p
unique_ptr獨有的操做
操做功能
unique_ptr<T> u指向類型T的對象的空unique_ptr,且默認使用delete釋放指針
unique_ptr<T, D> u指向類型T的對象的空unique_ptr,且使用類型爲D的可調用對象來釋放指針
unique_ptr<T, D> u(d)指向類型T的對象的空unique_ptr,且使用類型爲D的可調用對象d代替delete來釋放指針
u = nullptr釋放u指向的內存,並將u置爲空
u.release()先使指針對象u放棄對指針(值)的控制權,再返回指針(值),最後將u置爲空——即斷開指針對象和所指對象之間的聯繫(只斷開聯繫,不釋放內存)
u1.reset()、u2.reset(q)、u3.reset(nullptr)重置指針(值),使其指向新對象或爲空
shared_ptr類
關鍵概念
初始化
用make_shared初始化(最安全)
拷貝初始化(拷貝另外一個智能指針)或直接初始化(可由任意類型指針爲參數)
引用計數
引用計數決定什麼時候釋放內存
智能指針獨有,內置指針沒有
相互關聯的智能指針的引用計數相同
內存釋放
析構函數
默認delete
自定義刪除器
初始化
使用make_shared標準庫函數初始化(最安全,且會爲對象分配內存):
shared_ptr<string> p1 = make_shared<string>("chensu");
shared_ptr<int> p2 = make_shared<int>(24);
shared_ptr<vector<string>> p3 = make_shared<vector<string>>(); // 當參數爲空時,默認進行值初始化,此時p3所指對象爲空vector<string>
class Person {
private:
string name;
int age;
public:
Person(const string &_name, int _age) : name(_name), age(_age) {}
void print() const { cout << name << ", " << age << endl; }
};
shared_ptr<Person> p4 = make_shared<Person>(Person("chensu", 24)); // 參數必須與Person類的某個構造函數相匹配
使用make_shared爲智能指針分配動態內存比用new直接初始化更安全的緣由:使得在分配對象的同時就將shared_ptr與之綁定,避免將同一塊內存綁定到多個獨立建立的shared_ptr上:
Person *p = new Person("chensu", 24);
shared_ptr<Person> p1(p);
shared_ptr<Person> p2(p); // 此時p1和p2相互獨立且引用計數都爲1,程序結束時會釋放統一內存兩次
拷貝初始化(只拷貝指針,無需從新爲對象分配內存)
拷貝智能指針
auto p = make_shared<int>(42); // 爲p指向的對象分配內存,且此時該對象只有p一個引用者
auto q(p); // p和q指向同一個對象,此時該對象有兩個引用者
拷貝普通指針(和new結合):因爲接受指針參數的智能指針構造函數是explicit的,所以咱們不能將一個普通指針隱式轉換爲智能指針,而必須使用直接初始化形式或將普通指針強制類型轉換爲智能指針。
shared_ptr<int> p = new int(42); // 錯誤,不支持隱式轉換,可使用直接初始化或強制類型轉換
shared_ptr<int> p(new int(42)); // 正確,支持直接初始化形式
shared_ptr<int> p = shared_ptr<int>(new int(42)); // 正確,可以拷貝強制轉換後的智能指針
初始化並自定義釋放操做:默認狀況下,系統經過delete運算符來自動釋放智能指針,所以一個用來初始化智能指針的普通指針必須指向動態內存,不然結果未定義。但咱們能夠自定義釋放操做來代替默認的delete,這樣就能夠用指向非動態內存的普通指針來初始化智能指針
shared_ptr<int> p(q, d); // p將使用可調用對象d來代替delete
引用計數
每一個shared_ptr都各自關聯一個計數器,以避免發生引用非法內存(即在尚有指針引用內存的狀況下就釋放該內存),引用計數的初始值爲1
智能指針的引用計數=所指對象綁定的互相關聯的智能指針的數量
智能指針必須互相關聯的緣由:拷貝智能指針有兩個階段,且這兩個階段只有互相關聯的智能指針才能完成,互相獨立的智能指針沒有這兩個階段
拷貝引用計數(將源智能指針的引用計數拷貝給目的智能指針)
共同遞增引用計數(分別將源智能指針和目的智能指針的引用計數加1)
例子:
shared_ptr<int> p(new int(42)); // p的引用計數爲1
shared_ptr<int> q(p); // 由於p和q相互關聯,因此它們的引用計數都遞增爲2
{
shared_ptr<int> r(p.get()); // 由於p和r相互獨立(經過get初始化的指針互相獨立),因此它們的引用計數都不變(既不拷貝,也不共同遞增),其中p爲2,r爲1
} // 程序塊結束,r遞減爲0,並釋放r所指向的內存,此時p和q都爲懸空指針
int a = *p; // 未定義,由於此時p爲懸空指針
永遠不要用get得到的指針值初始化一個智能指針,由於這樣作的話,源指針和目的智能指針是相互獨立的
指向同一對象的互相關聯的智能指針具備相同的引用計數
智能指針p所指對象的引用計數遞增的狀況——即p所指的對象與和p互相關聯的其它智能指針發生新的綁定關係:
用p初始化另外一個智能指針q(對象綁定另外一個智能指針):
auto p = make_shared<int>(42); // 此時p的引用計數爲1
auto q = p; // 此時p和q的引用計數爲2
將p做爲參數傳遞給一個函數(對象綁定形參):
auto p = make_shared<int>(42); // 此時p的引用計數爲1
function(p); // 此時p的引用計數爲2
將p做爲函數的返回值(對象綁定一個右值指針或左值指針):
shared_ptr<int> function() {
auto p = make_shared<int>(42); // 此時p的引用計數爲1
return p; // 此時p的引用計數爲2
}
... // 即便離開了function函數,p所指的對象的引用計數依舊爲1
智能指針p所指對象的引用計數遞減的狀況——即p所指的對象與和p互相關聯的其它智能指針解除舊的綁定關係:
爲p賦予一個和原來不一樣的新值:
auto p = make_shared<int>(42);
auto p = nullptr; // 此時p的引用計數爲0
p被銷燬(例如程序離開p的做用域):
void function() {
auto p = make_shared<int>(42); // 此時`p`的引用計數爲1
}
... // 此時`p`的引用計數爲0
一旦一個shared_ptr的引用計數變爲0,它就會使用析構函數自動釋放本身所管理的對象(相似於java中的垃圾回收機制)
內存釋放
析構函數: 智能指針經過本身的析構函數完成銷燬操做,析構函數會遞減它所指向對象的引用計數,若引用計數變爲0(即該對象此時沒有綁定任何指針),則析構函數會銷燬對象,並釋放對象所佔用的內存
注意事項
因爲在最後一個shared_ptr銷燬以前內存都不會釋放,所以保證shared_ptr在無用以後再也不保留就很是重要了,若你忘記銷燬程序中再也不須要的shared_ptr,雖不會操做內存泄露,但會浪費內存,形成程序運行較卡
銷燬再也不須要的shared_ptr指針的方法是令其爲nullptr
默認狀況下,系統經過delete運算符來自動釋放智能指針,所以一個用來初始化智能指針的普通指針必須指向動態內存,不然結果未定義。但咱們能夠自定義釋放操做來代替默認的delete,這樣就能夠用指向非動態內存的普通指針來初始化智能指針
警告
不要混合使用普通指針和智能指針——由於混合使用會干擾內存的正常釋放
void process(shared_ptr<int> ptr) { ... } // 指針進入函數時引用計數加1,離開時引用計數減1
/* 狀況1:只使用智能指針 */
shared_ptr<int> p(new int(42)); // p所指內存的引用計數爲1
process(p); // p離開後引用計數不變,仍爲1
int i = *p; // 正確,由於內存未被釋放
/* 狀況2:混合使用智能指針和普通指針 */
int *q(new int(42)); // q所指內存的引用計數爲0
process(shared_ptr<int>(q)); // q離開後引用計數不變,仍未0,但在離開時因爲形參的類型爲shared_ptr,所以系統會自動釋放q所指向的內存,此時q爲懸空指針
int j = *q; // 未定義,此時q爲懸空指針
unique_ptr類
關鍵概念
初始化
拷貝初始化(拷貝另外一個智能指針)或直接初始化(可由任意類型指針爲參數)
對指針(值)的控制權
全部操做都必須保持指針值的惟一性
某一時刻只能有一個unique_ptr指向一個給定的對象——保持惟一性
不能拷貝或賦值unique_ptr(例外:能夠拷貝或賦值一個將要被銷燬的unique_ptr)——保持惟一性:
unique_ptr<int> clone(int p) {
return unique_ptr<int>(new int(p));
}
unique_ptr<int> q = clone(1); // 正確,該函數的返回值是一個右值(臨時變量)
容許轉移對指針(值)的控制權,但源指針值必須改變——保持惟一性
內存釋放
當unique_ptr爲nullptr時,其原來所指向的內存會被釋放
對象與unique_ptr指針之間的聯繫——若聯繫斷開,則即便unique_ptr指針被銷燬,對象也不會被釋放
只有與對象相關聯的unique_ptr被銷燬時,對象纔會被自動釋放
對unique_ptr指針(值)的控制權
使用release()釋放對指針值或對象內存的控制權(但不會釋放所指對象的內存)
{
unique_ptr<int> u(new int(1));
u.release(); // 釋放控制權(斷開u和對象之間的聯繫)
} // 離開做用域,銷燬u,但因爲此時u和對象之間無關聯,所以沒法釋放對象內存,故形成內存泄漏
使用release()和reset()轉移控制權(目的unique_ptr原來所指向的對象會被自動釋放)
{
unique_ptr<int> u1(new int(1));
unique_ptr<int> u2(new int(2));
u2.reset(u1.release()); // u1先釋放對指針值的控制權(斷開與對象的聯繫),再返回指針值給u2,接着將u1置爲空,此時u2掌握該指針值(或對象內存)的控制權,最後自動釋放以前u2控制的內存
} // 離開做用域,釋放此時u2控制的內存
向unique_ptr傳遞刪除器
默認狀況下,使用delete釋放它指向的對象
因爲咱們必須在尖括號中unique_ptr指向的對象類型以後提供刪除器類型,所以重載一個刪除器會影響到unique_ptr的類型以及如何構造(或reset)該類型的對象:
void my_deleter(int *ptr) { delete ptr; }
int *q = new int(1);
unique_ptr<int, decltype(my_deleter) *)> p(q, my_deleter)
weak_ptr類
將一個weak_ptr綁定到shared_ptr上不會改變shared_ptr的引用計數
一旦weak_ptr綁定的最後一個shared_ptr被釋放,該weak_ptr就成爲懸空指針
因爲對象可能已被釋放(weak_ptr成爲了懸空指針),所以咱們不能使用weak_ptr直接訪問對象,而必須調用lock檢查weak_ptr所指的對象是否存在,若存在則返回一個shared_ptr:
shared_ptr<int> p = make_shared<int>(42);
weak_ptr<int> wp(p);
shared_ptr<int> q = wp.lock();
總結
智能指針要特別注意同一內存屢次釋放和內存爲釋放問題
shared_ptr的引用計數決定是否釋放其所指向的內存
始終保證至少有一個unique_ptr和對象之間的聯繫,不然容易形成內存泄漏