內存分配:
靜態存儲區:
- 局部static對象
- 類的static數據成員
- 定義在任何函數之外的變量
棧區:
- 函數內的非static對象
動態內存分配的方式有:
- new和delete
- 智能指針(shared_ptr、unique_ptr、weak_ptr)
- allocator類
- malloc和free
直接管理內存:
運算符new分配內存,delete釋放new分配的內存。
int* p = new int ();//new表達式在自由空間構造一個對象,并返回指向該對象的指針。
delete p;
int* pi = new int[10]();//分配一個10個int的數組,pi指向第一個int。
默認情況下,動態分配的對象是默認初始化的,這意味著內置類型或者組合類型的對象的值是未定義的,而類類型對象將用默認構造函數進行初始化。
用new分配const對象是合法的,但const對象必須進行初始化。
如果一個程序用光了所有可用的內存,new表達式會失敗,并拋出bad_alloc異常,該異常可以通過nothrow阻止,阻止異常拋出的new稱為定位new。
int* p1 = new int;//如果分配失敗,new拋出std::bad_alloc。
int* p2 = new (nothrow)int;//如果分配失敗,new返回一個空指針。
delete表達式也執行兩個動作:銷毀一個給定的指針指向的對象;釋放對應的內存。
傳遞給delete的指針必須指向動態分配的內存,或者是一個空指針,釋放一個并非new分配的內存,或者將相同的指針值釋放多次,其行為是未定義的。
釋放動態數組時,數組中的元素按逆序銷毀。
釋放一個const動態對象,只要delete指向它的指針即可。
const int* pci = new const int(1024);
delete pci;
直接管理內存容易犯的錯:
- 忘記delete內存(內存泄漏)
- 使用已經釋放掉的對象
- 同一塊內存釋放兩次
空懸指針/野指針(dangling pointer):指向一塊曾經保存數據對象但現在已經無效的內存的指針。
產生原因:指針變量聲明時未初始化或者指針被delete或free后未置為nullptr以及指針操作超越了變量的作用范圍。
智能指針:
shared_ptr允許多個指針指向同一個對象
unique_ptr“獨占”所指向的對象
weak_ptr指向一個shared_ptr管理的對象,它不控制指向對象的生存期。
shared_ptr:
初始化:
shared_ptr<string> p1;//默認初始化的智能指針保存著一個空指針
智能指針的使用方式與普通指針類似。解引用一個智能指針返回它指向的對象,如果在一個條件判斷中使用智能指針,效果是檢測它是否為空。
if (p1 && p1->empty()) {*p1 = "hi";//如果p1指向一個空string,解引用p1,將一個新值賦予string。
}
make_shared函數:
類似順序容器的emplace成員,make_shared用其參數來構造給定類型的對象。
shared_ptr<int> pi = make_shared<int>(42);
引用計數:
每個shared_ptr都有一個關聯的計數器,通常稱其為引用計數。無論何時我們拷貝一個shared_ptr,或將其作為參數傳遞給一個函數以及作為一個函數的返回值時,它所關聯的計數器就會遞增。當我們給shared_ptr賦予一個新值或者是shared_ptr被銷毀時,計數器就會遞減。當一個shared_ptr的計數器變為0,他就會自動釋放自己所管理的對象。
程序使用動態內存的三個原因:
- 程序不知道使用多少個對象(容器類)
- 程序不知道所需對象的具體類型
- 程序需要在多個對象間共享數據
shared_ptr與new的結合:
接受指針參數的智能指針構造函數是explicit的,因此我們不能將一個內置指針隱式轉換為一個智能指針,必須使用直接初始化的形式。
shared_ptr<int> p1 = new int(1024);//錯誤
shared_ptr<int> p1(new int(1024));//正確
如果想用shared_ptr管理動態數組,必須提供自己定義的刪除器,并且不支持下標操作。
shared_ptr<int> sp(new int[10],[](int *p){delete [] p ;});
sp.reset();
正確使用智能指針的規范:
- 使用相同的內置指針值初始化(或reset)多個智能指針。
- 不delete get() 返回的指針。
- 不使用get()初始化或reset另一個智能指針。
- 如果你使用get()返回的指針,記住當最后一個對應的智能指針銷毀后,你的指針就變為無效了。
- 如果你使用智能指針管理的資源不是new分配的內存,記住傳遞給它一個刪除器。
unique_ptr:
當我們定義unique_ptr時,需要將其綁定到一個new返回的指針上,而且必須采用直接初始化的形式。
由于unique_ptr“獨占”它指向的對象,因此unique_ptr不支持拷貝或賦值操作。
雖然我們不能拷貝和賦值unique_ptr,但可以調用release或reset將指針所有權從一個(非const)unique_ptr轉移給另一個unique:
unique_ptr<string> p1(new string("first"));
unique_ptr<string> p2(p1.release());//將所有權從p1轉向p2,p1被置為空
unique_ptr<string> p3(new string ("second"));
//將所有權從p3轉移給p2.
p2.reset(p3.release());//reset釋放了p2原來指向的內存。
unique_ptr可以管理動態數組,必須在對象類型后面跟一對空方括號,支持下標操作。
unique_ptr<int[]> up(new int[10]);
up.release();//自動用delete[]銷毀其指針。
for(size_t i = 0 ; i != 10 ; i++)
{up[i] = i;
}
weak_ptr:
weak_ptr不控制所指向的對象的生存期,指向一個由shared_ptr管理的對象。將一個weak_ptr不會改變shared_ptr的引用計數。一旦最后一個指向對象的shared_ptr被銷毀,對象就會被釋放。
由于對象可能不存在,不能使用weak_ptr直接訪問對象,必須調用lock。此函數檢查weak_ptr指向的對象是否仍存在。如果存在,lock返回一個指向共享對象的shared_ptr。
可以解決兩個shared_ptr互相引用的問題。
allocator類:
將內存分配和對象構造分離開,提供一種類型感知的內存分配方法,他分配的內存是原始的、未構造的。
定義一個allocator對象,必須指明這個allocator可以分配的對象類型。當一個allocator對象分配內存時,它會根據給定的對象類型來確定恰當的內存大小和對齊位置:
allocator<string> alloc;//可以分配string的allocator對象
auto const p = alloc.allocate(n);//分配n個未初始化的string。
auto q = p;
alloc.construct(q++);//*q為空字符串
alloc.construct(q++,10,'c');//*q為cccccccccc
alloc.construct(q++,"hi");//*q為hi
cout<<*p<<endl;//正確:使用string的輸出運算符
cout<<*q<<endl;//災難:q指向未構造的內存
while(q!=p)alloc.destroy(--q);//釋放我們真正構造的string
//destory接受一個指針,對指向的對象執行析構函數。
alloc.deconstruct(p,n);//通過deallocate釋放內存。
//傳遞給deallocate的指針不能為空,它必須指向由allocate分配的內存。而且傳遞給deallocate的大小參數必須與調用allocated分配內存時提供的大小參數具有一樣的值。