直接管理內存
什麼時候需要直接管理
簡而言之,當內存分配在棧上時,不需要直接管理,而當內存分配在堆上時則需要手動回收,或者等到堆上內存分配滿了觸發了自動回收機制。
關於堆和棧,這篇文章講得淺顯易懂:http://blog.csdn.net/hairetz/article/details/4141043
一個由C/C++編譯的程序佔用的內存分爲以下幾個部分
- 棧區(stack)—— 由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧。
- 堆區(heap)—— 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收。注意它與數據結構中的堆是兩回事,分配方式倒是類似於鏈表。
- 全局區(靜態區)(static)——全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。程序結束後由系統釋放。
- 文字常量區——常量字符串就是放在這裏的,程序結束後由系統釋放 。
- 程序代碼區——存放函數體的二進制代碼。
例子程序
這是一個前輩寫的,非常詳細
//main.cpp
int a = 0; 全局初始化區
char *p1; 全局未初始化區
main() {
int b; 棧
char s[] = "abc"; 棧
char *p2; 棧
char *p3 = "123456"; 123456/0在常量區,p3在棧上。
static int c =0; 全局(靜態)初始化區
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得來得10和20字節的區域就在堆區。
strcpy(p1, "123456"); 123456/0放在常量區,編譯器可能會將它與p3所指向的"123456" 優化成一個地方。
}
注意,除了上文的malloc,new分配的內存也在堆中需要手動銷燬。
動態內存
由上文看出,分配在堆上的內存需要手動進行動態分配和釋放,我們將之稱爲動態內存。C++中,動態內存是通過new和delete來進行分配和釋放的。
new:在動態內存中爲對象分配空間並返回一個指向該對象的指針,我們可以選擇對對象進行初始化。
delete:接受一個動態對象的指針,銷燬該對象,並釋放與之關聯的內存。
在自由空間分配的內存是無名的,因此new無法爲其分配的對象命名,而是返回一個指向該對象的指針。
int *pi=new int; //pi指向一個動態分配的,未初始化的無名對象
可以是使用直接初始化方式來初始化一個動態分配的對象。操作 | 說明 |
---|---|
shared_ptr<T> sp , unique_ptr<T>
up |
空智能指針,可以指向類型爲T的對象 |
p | 將p用作一個條件判斷,若p指向一個對象,則爲true |
*p | 解引用p,獲得它指向的對象 |
p->mem | 等價於(*p).mem |
p.get() | 返回p中保存的指針。要小心使用,若智能指針釋放了其對象,返回的指針所指向的對象也就消失了 |
swap(p,q) ,p.swap(q) | 交換p和q中的指針 |
shared_ptr類
創建一個智能指針時,必須提供額外的信息——指針可以指向的類型。
shared_ptr<string> p1;
shared_ptr<list<int>> p2;
shared_ptr獨有的操作
操作 | 說明 |
---|---|
make_shared<T>(args) |
返回一個shared_ptr,指向一個動態分配的類型爲T的對象。使用args初始化此對象 |
shared_ptr<T>p(q) |
p是shared_ptr q的拷貝;此操作會遞增q中的計數器。q中的指針必須能轉換爲T* |
p=q | p和q都是shared_ptr,所保存的指針必須能相互轉換。此操作會遞減p的引用計數,遞增q的引用計數;若p的引用計數變爲0,則將其管理的原內存釋放 |
p.unique() | 若p.use_count()爲1,返回true;否則返回false |
p.use_count() | 返回與p共享對象的智能指針數量;可能很慢,主要用於調試 |
make_shared函數
最安全的分配和使用動態內存的方法是調用一個名爲make_shared的標準庫函數。此函數在動態內存中分配一個對象並初始化它,返回指向此對象的shared_ptr。
shared_ptr<int> p3=make_shared<int>(42);
auto p6=make_shared<vector<string>>();
shared_ptr的拷貝和賦值
當進行拷貝或賦值操作時,每個shared_ptr都會記錄有多少個其他shared_ptr指向相同的對象。
shared_ptr自動銷燬所管理的對象
shared_ptr的析構函數會遞減它所指向的對象的引用計數。如果引用計數變爲0,shared_ptr的析構函數就會銷燬對象,並釋放它佔用的內存。
shared_ptr還會自動釋放相關聯的內存
return會對shared_ptr指針的引用次數進行遞增操作。
使用了動態生存期的資源的類
程序使用動態內存出於以下三種原因之一:
1. 程序不知道自己需要使用多少對象
2. 程序不知道所需對象的準確類型
3. 程序需要在多個對象間共享數據
shared_ptr和new結合使用
接受指針參數的智能指針構造函數是explicit的,我們不能將一個內置指針隱式轉換爲一個智能指針,必須使用直接初始化形式來初始化一個智能指針:
shared_ptr<int> p1=new int(1024); //錯誤
shared_ptr<int> p2(new int(1024)); //正確
shared_ptr<int> clone(int p){
return new int(p); //錯誤
}
shared_ptr<int> clone(int p){
return shared_ptr<int>(new int(p)); //正確
}
定義和改變shared_ptr的其他方法
操作 | 說明 |
---|---|
shared_ptr<T> p(q) |
p管理內置指針q所指向的對象;q必須指向new分配的內存,且能夠轉換爲T*類型 |
shared_ptr<T> p(u) |
p從unique_ptr u那裏接管了對象的所有權;將u置爲空 |
shared_ptr<T> p(q,d) |
p接管了內置指針q所指向的對象的所有權。q必須能轉換爲T*類型。p將使用可調用對象d來代替delete |
shared_ptr<T> p(p2,d) |
p是shared_ptr p2的拷貝,唯一的區別是p將用可調用對象d來代替delete |
p.reset()p,reset(q)p,reset(q,d) | 若p是唯一指向其對象的shared_ptr,reset會釋放此對象。若傳遞了可選的參數內置指針q,會令p指向q,否則將p置爲空。若還傳遞了參數d,將會調用d而不是delete來釋放q |
不要混合使用普通指針和智能指針
void process(shared_ptr<int> ptr){}
process的參數是傳值方式傳遞的,因此實參會被拷貝到ptr中。拷貝一個shared_ptr會遞增其引用計數,因此,在process運行過程中,引用計數值至少爲2.當process結束時,ptr的引用計數會遞減,但不會變爲0.因此當局部變量ptr被銷燬時,ptr指向的內存不會被釋放。 正確方式是傳遞給它一個shared_ptr:
shared_ptr<int> p(new int(42)); //引用計數爲1
process(p); //
雖然不能傳遞給process一個內置指針,但可以傳遞給它一個(臨時的)shared_ptr,這個shared_ptr是用一個內置指針顯式構造的。但是,這樣做很可能會導致錯誤:int *x(new int(1024)); //危險:x是一個普通指針,不是一個智能指針
process(x); //錯誤
process(shared_ptr<int>(x)); //合法的,但內存會被釋放
int j=*x; //未定義的:x是一個空懸指針
不要使用get初始化另一個智能指針或爲智能指針賦值 智能指針類型頂一個了一個名爲get的函數,它返回一個內置指針,指向智能指針管理的對象。此函數是爲了這樣一種情況兒設計的:我們需要向不能使用智能指針的代碼傳遞一個內置指針。使用get返回的指針的代碼不能delete此指針。
shared_ptr<int> p(new int(42)); //引用計數爲1
int *q=p.get(); //正確:但使用q時要注意,不要讓它管理的指針被釋放
{
//未定義:兩個獨立的shared_ptr指向相同的內存
shared_ptr<int>(q);
}//程序塊結束,q被銷燬,它指向的內存被釋放
int foo=*p; //未定義:p指向的內存已經被釋放了
智能指針和異常
如果使用智能指針,即使程序塊過早結束,智能指針類也能確保在內存不再需要時將其釋放。
智能指針和啞類
使用我們自己的釋放操作
爲了正確使用智能指針,我們必須堅持一些基本規範:
- 不適用相同的內置指針初始化(或reset)多個智能指針。
- 不delete get()返回的指針。
- 不使用get()初始化或reset另一個智能指針。
- 如果你使用get()返回的指針,記住當最後一個對應的智能指針銷燬後,你的指針就變爲無效了。
- 如果你用智能指針管理的資源不是new分配的內存,記住傳遞給它一個刪除器。
unique_ptr
一個unique_ptr“擁有”它所指向的對象。與shared_ptr不同,某個時刻只能有一個unique_ptr指向一個給定對象。當unique_ptr被銷燬時,它所指向的對象也被銷燬。
與shared_ptr不同,沒有類似make_shared的標準庫函數返回一個unique_ptr。當我們定義一個unique_ptr時,需要將其綁定到一個new返回的指針上。尅死shared_ptr,初始化unique_ptr必須採用直接初始化形式:
unique_ptr<double> p1; //可以指向一個double的unique_ptr
unique_ptr<int> p2(new int(42));//p2指向一個值爲42的int
由於一個unique_ptr擁有它指向的對象,因此unique_ptr不支持普通的拷貝或賦值操作
unique_ptr操作
操作 | 說明 |
---|---|
unique_ptr<T> u1 ,unique_ptr<T,D>
u2 |
空unique_ptr,可以指向類型爲T的對象,u1會使用delete來釋放它的指針;u2會使用一個類型爲b的可調用對象來釋放它的指針 |
unique_ptr<T,D> u(d) |
空unique_ptr,指向類型爲T的對象,用類型爲D的對象d代替delete |
u=nullptr | 釋放u指向的對象,將u置爲空 |
u.release() | u放棄對指針的控制權,返回指針,並將u置爲空 |
u.reset() | 釋放u指向的對象 |
u.reset(q) ,u.reset(nullptr) | 如果提供了內置指針q,令u指向這個對象;否則將u置爲空 |
//將所有權從p1轉移給p2
unique_ptr<string> p2(p1.release()); //release將p1置爲空
unique_ptr<string> p3(new string(“Trex”));
//將所有權從p3轉移給p2
p2.reset(p3.release());; //reset釋放了p2原來指向的內存
傳遞unique_ptr參數和返回unique_ptr
不能拷貝unique_ptr的規則有一個例外:我們可以拷貝或賦值一個將要被銷燬的unique_ptr。最常見的例子是從函數返回一個unique_ptr。
向unique_ptr傳遞刪除器
weak_ptr
weak_ptr指向由一個shared_ptr管理的對象。將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數。
weak_ptr
操作 | 說明 |
---|---|
weak_ptr<T> w |
空weak_ptr可以指向類型爲T的對象 |
weak_ptr<T> w(sp) |
與shared_ptr sp指向相同對象的weak_ptr。T必須能轉換爲sp指向的類型 |
w=p | p可以是一個shared_ptr或一個weak_ptr。賦值後w與p共享對象 |
w.reset() | 將w置爲空 |
w.use_count() | 與w共享對象的shared_ptr的數量 |
w.expired() | 若w.use_count()爲0,返回true,否則返回false |
w.lock() | 如果expired爲true,返回一個空shared_ptr;否則返回一個指向w的對象的shared_ptr |
動態數組
new和delete運算符一次分配/釋放一個對象,但某些應用需要一次爲很多對象分配內存的功能。例如,vector和string都是在連續內存中保存它們的元素,因此,當容器需要重新分配內存時,必須一次性爲很多元素分配內存。
爲了支持這種需求,C++語言和標準庫提供了兩種一次分配一個對象數組的方法:C++語言定義了動態數組的new方式;標準庫中包含了一個名爲allocator的類。
new和數組
int *pia=new int[get_size()]; //pia指向第一個int
分配一個數組會得到一個元素類型的指針 雖然我們通常稱new T[]分配的內存爲“動態數組”,但我們用new分配一個數組時,並未得到一個數組類型的對象,而是一個數組元素類型的指針。
由於分配的內存並不是一個數組類型,因此不能對動態數組調用begin或end。這些函數使用數組維度來返回指向首元素和尾後元素的指針。出於相同的原因,也不能用範圍for語句來處理動態數組中的元素。
初始化動態分配對象的數組
可以對數組中的元素進行值初始化,方法是在大笑之後跟一對空括號:
int *pia=new int[10]; //10個未初始化的int
int *pia2=new int[10](); //10個值初始化爲0的int
動態分配一個空數組是合法的 雖然我們不能創建一個大小爲0的靜態數組對象,但當n等於0時,調用new[n]是合法的:
char arr[0]; //錯誤
char *cp=new char[0]; //正確
釋放動態數組 爲了釋放動態數組,我們使用一種特殊形式的delete——在指針前加上一個空方括號對:
delete p; //p必須指向一個動態分配的對象或爲空
delete [] pa; //pa必須指向一個動態分配的數組或爲空
數組中的元素按逆序被銷燬。
智能指針和動態數組
爲了用一個unique_ptr管理動態數組,我們必須在對象類型後面跟一對空方括號:
//up指向一個包含10個未初始化int的數組
unique_ptr<int[]> up(new int[10]);
up.release(); //自動用delete[]銷燬其指針
指向數組的unique_ptr
指向數組的unique_ptr不支持成員訪問運算符(點和箭頭運算符)
其他unique_ptr操作不便
操作 | 說明 |
---|---|
unique_ptr<T[]> u |
u可以指向一個動態分配的數組,數組元素類型爲T |
unique_ptr<T[]> u(p) |
u指向內置指針p所指向的動態分配的數組。p必須能轉換爲類型T* |
u[i] | 返回u擁有的數組中的位置i處的對象,u必須指向一個數組 |
allocator類
allocator類
標準庫allocator類定義在頭文件memory中,它幫助我們將內存分配和對象構造分離開來。它提供一種類型感知的內存分配方法,它分配的內存是原始的、未構造的。
類似vector,allocator是一個模板。爲了定義一個allocator對象,我們必須指明這個allocator可以分配的對象類型。當一個allocator對象分配內存時,他會根據給定的對象類型來確定恰當的內存大小和對齊位置:
allocator<string> alloc; //可以分配string的allocator對象
auto const p=alloc.allocate(n); //分配n個未初始化的string
標準庫allocator類及其算法
操作 | 說明 |
---|---|
allocator a | 定義了一個名爲a的allocator對象,它可以爲類型爲T的對象分配內存 |
a.allocate(n) | 分配一段原始的、爲構造的內存,保存n個類型爲T的對象 |
a.deallocate(p,n) | 釋放從T*指針p中地址開始的內存,這塊內存保存了n個類型爲T的對象;p必須是一個先前由allocate返回的指針,且n必須是p創建時所要求的大小。在調用deallocate之前,用戶必須對每個在這塊內存中創建的對象調用destroy |
a.construct(p,args) | p必須是一個類型爲T*的指針,指向一塊原始內存;arg被傳遞給類型爲T的構造函數,用來在p指向的內存中構造一個對象 |
a.destroy(p) | p爲T*類型的指針,此算法對p指向的對象執行西溝函數 |
allocator分配爲構造的內存
allocator分配的內存是未構造的,我們按需要在此內存中構造對象。
auto q=p; //q指向最後構造的元素之後的位置
alloc.construct(q++); //*q爲空字符串
alloc.construct(q++,10,’c’); //*q爲cccccccccc
alloc.construct(q++,”hi”); //*q位hi!
爲了使用allocate返回的內存,我們必須用construct構造對象。使用爲構造的內存,其行爲是未定義的。
我們只能對真正構造了的元素進行destroy操作
拷貝和填充未初始化內存的算法
它們都定義在頭文件memory中
allocator算法
這些函數在給定目的位置創建元素,而不是由系統分配內存給它們。
操作 | 說明 |
---|---|
uninitialized_copy(b,e,b2) | 從迭代器b和e指出的輸入範圍中拷貝元素到迭代器b2指定的爲構造的原始內存中。b2指向的內存必須足夠大,能容納輸入序列中元素的拷貝 |
uninitialized_copy(b,n,b2) | 從迭代器b指向的元素開始,拷貝n個元素到b2開始的內存中 |
uninitialized_fill(b,e,t) | 在迭代器b和e指定的原始內存範圍中創建對象,對象的值均爲t的拷貝 |
uninitialized_fill_n(b,n,t) | 從迭代器b指向的內存地址開始創建n個對象。b必須指向足夠大的爲構造的原始內存,能夠容納給定數量的對象 |
轉載請註明出處:http://blog.csdn.net/ylbs110/article/details/51049586