除了自動和static對象外憔披,C++還支持動態(tài)分配對象等限。動態(tài)分配的對象的生存期與它們在哪里創(chuàng)建是無關(guān)的,只有當(dāng)顯式地被釋放時,這些對象才會銷毀精刷。
- 靜態(tài)內(nèi)存用來保存局部static對象,類static數(shù)據(jù)成員蔗候,以及定義在任何函數(shù)之外的變量怒允。棧內(nèi)存用來保存定義在函數(shù)內(nèi)的非static對象。分配在靜態(tài)內(nèi)存或棧內(nèi)存的對象由編譯器自動創(chuàng)建和銷毀锈遥。
- 除了靜態(tài)內(nèi)存和棧內(nèi)存纫事,每個程序還擁有一個內(nèi)存池。這部分內(nèi)存被稱作自由空間(free store)或堆(heap)所灸。程序用堆來存儲動態(tài)分配的對象——即程序運(yùn)行時分配的對象丽惶。動態(tài)對象的生存期由程序而不是編譯器來控制。
- C++中爬立,動態(tài)內(nèi)存的管理是通過一對運(yùn)算符來完成的:new钾唬,在動態(tài)內(nèi)存中為對象分配空間并返回一個指向該對象的指針;delete侠驯,接受一個動態(tài)對象的指針抡秆,銷毀該對象,并釋放與之關(guān)聯(lián)的內(nèi)存吟策。
- 為了更容易儒士,更安全地使用動態(tài)內(nèi)存,新的標(biāo)準(zhǔn)庫提供了兩種智能指針(smart pointer)類型來管理動態(tài)對象檩坚。智能指針的行為類似常規(guī)指針着撩,重要的區(qū)別是它負(fù)責(zé)自動釋放所指向的對象。shared_ptr允許多個指針指向同一個對象匾委;unique_ptr則“獨(dú)占”所指向的對象拖叙。標(biāo)準(zhǔn)庫還定義了一個名為weak_ptr的伴隨類,它是一種弱引用剩檀,指向shared_ptr所管理的對象憋沿,這三種類型都定義在memory頭文件中。
- 類似vector沪猴,智能指針也是模板辐啄,因此當(dāng)我們創(chuàng)建智能指針時,必須提供額外信息——指針可以指向的類型运嗜。
shared_ptr<string> p1;
shared_ptr<list<int>> p2;
默認(rèn)初始化的智能指針保存著一個空指針壶辜。
智能指針的使用方式與普通指針類似,解引用返回它指向的對象担租,如果在條件判斷中使用智能指針砸民,效果就是檢測它是否為空。
- 最安全的分配和使用動態(tài)內(nèi)存的方法是調(diào)用一個名為make_shared的標(biāo)準(zhǔn)庫函數(shù)。此函數(shù)在動態(tài)內(nèi)存中分配一個對象并初始化它岭参,返回指向此對象的shared_ptr反惕。該函數(shù)同樣定義在memory中。
shared_ptr<int> p3=make_shared<int>(42);
shared_ptr<string> p4=make_shared<string>(10,'9);
shared_ptr<int> p5=make_shared<int>();
如果我們不傳遞參數(shù)演侯,對象會進(jìn)行值初始化姿染。
- 當(dāng)進(jìn)行拷貝或賦值操作時,每個shared_ptr都會記錄有多少個其他shared_ptr指向相同的對象:
auto p=make_shared<int>(42);
auto q(p); //p和q指向相同對象秒际,此對象有兩個引用者
我們可以認(rèn)為每個shared_ptr都有一個關(guān)聯(lián)的計數(shù)器悬赏,通常稱為引用計數(shù)(reference count),無論我們何時拷貝一個shared_ptr娄徊,計數(shù)器都會遞增闽颇。當(dāng)我們給shared_ptr賦予一個新值或是shared_ptr被銷毀(例如一個局部的shared_ptr離開作用域),計數(shù)器就會遞減寄锐。
- 當(dāng)指向一個對象的最后一個shared_ptr被銷毀時离熏,shared_ptr類會自動銷毀此對象杀餐。它是通過析構(gòu)函數(shù)實(shí)現(xiàn)的。
- 程序使用動態(tài)內(nèi)存出于以下三種原因之一:
- 程序不知道自己需要使用多少對象
- 程序不知道所需對象的準(zhǔn)確類型
- 程序需要在多個對象間共享數(shù)據(jù)
class SharedVector{
public:
SharedVector(initializer_list<string> l):data(make_shared<vector<string>>(l)){}
shared_ptr<vector<string>> data;
};
當(dāng)我們拷貝SharedVector對象時,會使用默認(rèn)版本的拷貝卒茬,因此Shared_ptr也被拷貝芍阎,從而實(shí)現(xiàn)了資源的對象間共享躁绸。
- C++定義了兩個運(yùn)算符new和delete來分配和釋放動態(tài)內(nèi)存吮铭。相對于智能指針,使用這兩個運(yùn)算符管理內(nèi)存非常容易出錯椎扬。
- 在自由空間分配的內(nèi)存是無名的惫搏,因此new無法為其分配的對象命名,而是返回一個指向該對象的指針:
int *pi=new int;
默認(rèn)情況下蚕涤,動態(tài)分配的對象是默認(rèn)初始化的筐赔,這意味著內(nèi)置類型或組合類型的值將是未定義的,而類類型對象將使用*默認(rèn)構(gòu)造函數(shù)進(jìn)行初始化:
string *ps=new string; //初始化為空string
int *pi=new int; //pi指向一個未初始化的int
我們可以使用直接初始化方式來初始化一個動態(tài)分配的對象揖铜。我們可以使用傳統(tǒng)的構(gòu)造方式(圓括號)茴丰,新標(biāo)準(zhǔn)下也可以使用列表初始化:
int *pi=new int(1024);
string *ps=new string(10,'9');
vector<int> *pv=new vector<int>{0,1,2,3,4,5,6,7,8};
也可以對動態(tài)分配的對象進(jìn)行值初始化,只需在類型名之后跟一對空括號即可天吓。
int *pi=new int(); //值初始化為0
對于定義了自己的構(gòu)造函數(shù)的類類型來說贿肩,要求值初始化沒有意義(對象都會通過默認(rèn)構(gòu)造函數(shù)來初始化),但對于內(nèi)置類型龄寞,值初始化意味著對象有良好定義的值汰规。
- 用new分配const對象是合法的,一個動態(tài)const對象必須進(jìn)行初始化
const int *pci=new const int(1024);
- 一旦一個程序用光了它所有可用的內(nèi)存物邑,new表達(dá)式就會失敗溜哮。默認(rèn)情況下滔金,它會拋出一個類型為bad_alloc的異常,我們可以改變new的方式來阻止拋出異常:
int *pw=new (nothrow) int; //如果分配失敗茂嗓,返回一個空指針
我們稱這種形式的new為定位new(placement new)餐茵,定位new允許我們向new傳遞額外的參數(shù)。此處我們傳遞了一個nothrow對象述吸。它與bad_alloc都定義在頭文件new中钟病。
- 為了防止內(nèi)存耗盡,我們通過delete 表達(dá)式來將動態(tài)內(nèi)存歸還給系統(tǒng)刚梭。delete接受一個指針,指向我們想要釋放的對象:
delete p; //p必須指向一個動態(tài)分配的對象或是一個空指針
- 釋放一塊并非new的內(nèi)存票唆,或者將相同的指針釋放多次朴读,其行為是未定義的。
- 由內(nèi)置指針管理的動態(tài)內(nèi)存在被顯式釋放前會一直存在走趋,如果不注意這一點(diǎn)很容易造成內(nèi)存泄漏衅金。
- 當(dāng)我們delete一個指針后,指針值就變?yōu)闊o效了簿煌,該指針就變成了所謂的空懸指針(dangling pointer)氮唯,即指向一塊曾經(jīng)保存數(shù)據(jù)對象但現(xiàn)在已經(jīng)無效的內(nèi)存的指針。如果我們需要保留指針姨伟,可以在delete之后賦予它nullptr的字面量惩琉。但實(shí)際上,可能有多個指針指向相同的內(nèi)存夺荒,這僅僅提供了很有限的保護(hù)瞒渠。
- 如前所述,我們?nèi)绻怀跏蓟粋€智能指針技扼,它就會被初始化為一個空指針伍玖。我們可以用new返回的指針來初始化智能指針:
shared_ptr<int> p1(new int(42));
接受指針參數(shù)的智能指針構(gòu)造函數(shù)是explicit的,所以必須使用直接初始化的形式剿吻。同理窍箍,一個返回shared_ptr的函數(shù)不能返回一個普通指針。
默認(rèn)情況下丽旅,一個用來初始化智能指針的普通指針必須指向動態(tài)內(nèi)存椰棘,因?yàn)橹悄苤羔樐J(rèn)使用delete釋放關(guān)聯(lián)的對象。但是我們可以提供操作替代delete使得智能指針可以綁定到一個指向其他類型資源的指針上榄笙。
- 當(dāng)將一個shared_ptr綁定到一個普通指針時晰搀,我們就將內(nèi)存的管理責(zé)任交給了這個shared_ptr。一旦這么做了办斑,就不應(yīng)該再使用內(nèi)置指針來訪問shared_ptr所指向的內(nèi)存了外恕。
- 如果使用智能指針杆逗,即使程序塊過早結(jié)束,智能指針類也能確保在內(nèi)存不再需要時將其釋放鳞疲。而直接管理的內(nèi)存如果在new之后delete之前發(fā)生了異常罪郊,則內(nèi)存不會釋放。
- 我們還可以利用智能指針來管理不具有良好定義的析構(gòu)函數(shù)的類尚洽。
- 為了正確使用智能指針悔橄,我們必須堅(jiān)持一些基本規(guī)范:
- 不使用相同的內(nèi)置指針值初始化多個智能指針
- 不delete get()返回的指針
- 不使用get()初始化另一個智能指針
- 如果你使用get()返回的指針,記住當(dāng)最后一個對應(yīng)的智能指針銷毀后腺毫,你的指針就變?yōu)闊o效了
- 如果你使用智能指針管理的資源不是new分配的內(nèi)存癣疟,記住傳遞給它一個刪除器
- 一個unique_ptr ”擁有“它所指向的對象,某個時刻只能有一個unique_ptr指向一個給定對象潮酒。當(dāng)它被銷毀時睛挚,指向的對象也被銷毀。
沒有類似make_shared的函數(shù)返回一個unique_ptr急黎。當(dāng)我們定義一個unique_ptr時扎狱,需要將其綁定到一個new返回的指針上,初始化也同樣必須采用直接初始化勃教。
unique_ptr<double> p1;
unique_ptr<int> p2(new int(10));
由于一個unique_ptr獨(dú)占它指向的對象淤击,因此不支持普通的拷貝或賦值操作。
- 雖然不能拷貝或賦值unique_ptr故源,但是可以通過release或reset將指針的所有權(quán)轉(zhuǎn)移:
p2.reset(p3.release());
- 不能拷貝unique_ptr的規(guī)則有一個例外:我們可以拷貝或賦值一個將要被銷毀的unique_ptr污抬。比如,從函數(shù)返回一個unique_ptr
- 與shared_ptr一樣绳军,我們可以重載一個unique_ptr中默認(rèn)的刪除器壕吹,此處不贅述
-
weak_ptr是一種不控制所指向?qū)ο笊嫫诘闹悄苤羔槪赶蛴梢粋€shared_ptr管理的對象删铃。將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數(shù)耳贬。一旦最后一個指向?qū)ο蟮膕hared_ptr被銷毀,對象就會被釋放猎唁,即使有weak_ptr指向?qū)ο蟆?br>
image
- 當(dāng)我們創(chuàng)建一個weak_ptr時咒劲,要用一個shared_ptr來初始化它:
auto p=make_shared<int>(42);
weak_ptr<int> wp(pP;
- 由于對象可能不存在,我們不能使用weak_ptr直接訪問對象诫隅,而必須調(diào)用lock腐魂,此函數(shù)檢查weak_ptr指向的對象是否仍存在。如果存在逐纬,則返回一個shared_ptr蛔屹。
- C++和標(biāo)準(zhǔn)庫提供了兩種一次分配一個對象數(shù)組的方法。C++定義了另一種new表達(dá)式語法豁生,可以分配并初始化一個對象數(shù)組兔毒。標(biāo)準(zhǔn)庫中包含一個名為allocator的類漫贞,允許我們將分配和初始化分離。
- 大多數(shù)應(yīng)用應(yīng)該使用標(biāo)準(zhǔn)庫容器而不是動態(tài)分配的數(shù)組育叁。使用容器更為簡單迅脐,更不容易出現(xiàn)內(nèi)存管理錯誤并且可能有更好的性能。
- 為了讓new分配一個對象數(shù)組豪嗽,我們要在類型名之后跟一對方括號谴蔑,在其中指明要分配的對象的數(shù)目,new返回指向第一個對象的指針:
int *p=new int[size];
- 雖然我們通常稱new T[]分配的內(nèi)存為“動態(tài)數(shù)組”龟梦,但實(shí)際上我們只是得到一個數(shù)組元素類型的指針隐锭。動態(tài)數(shù)組并不是數(shù)組類型,因此前文所述的begin和end函數(shù)计贰,以及范圍for語句钦睡,通通不適用。
- 默認(rèn)情況下蹦玫,new分配的對象,都是默認(rèn)初始化的刘绣∮8龋可以對數(shù)組中的元素進(jìn)行值初始化,方法是在大小之后跟一對空括號:
int *p=new int[10](); //10個值初始化為0的int
新標(biāo)準(zhǔn)中纬凤,我們還可以提供一個元素初始化器的花括號列表:
int *p=new int[2]{1,2};
- 可以用任意表達(dá)式來確定要分配的對象的數(shù)目:
size_t n=get_size();
int *p=new int[n];
- 當(dāng)我們用new分配一個大小為0的數(shù)組福贞,new返回一個合法的非空指針。但此指針不能解引用停士。
- 為了釋放動態(tài)數(shù)組挖帘,我們使用一種特殊形式的delete——指針前加上一個空括號對:
delete [ ] p;
數(shù)組中的元素按逆序銷毀。如果沒有方括號恋技,行為是未定義的拇舀。
- 標(biāo)準(zhǔn)庫提供了一個可以管理new分配的數(shù)組的unique_ptr版本,為了用一個unique_ptr管理動態(tài)數(shù)組蜻底,我們必須在對象類型后面跟一對空方括號:
unique_ptr<int[]> up(new int[10]);
up.release(); //自動用delete[]銷毀其指針
- shared_ptr不直接支持管理動態(tài)數(shù)組骄崩,必須提供自己定義的刪除器,此處不展開薄辅。
- new有一些靈活性上的局限要拂,其中一方面表現(xiàn)在它將內(nèi)存分配和對象構(gòu)造組合在了一起。標(biāo)準(zhǔn)庫allocator類定義在頭文件memory中站楚,它幫助我們將內(nèi)存分配和對象構(gòu)造分離開來脱惰。它提供一種類型感知的內(nèi)存分配方式,它分配的內(nèi)存是原始的窿春,未構(gòu)造的拉一。
image
allocator<string> alloc; // 可以分配 string 的 allocator 對象
auto const p = alloc.allocate(n); // 分配 n 個未初始化的 string
auto q = p; // q 指向最后構(gòu)造的元素之后的位置
alloc.construct(q++); // *q 為空字符串
alloc.construct(q++, 10, 'c'); // *q 為 cccccccccc
alloc.construct(q++, "hi"); // *q 為 hi
while(q != p)
alloc.destroy(--q); // 釋放我們真正構(gòu)造的 string
alloc.deallocate(p, n); // 釋放內(nèi)存
- 為了使用allocator返回的內(nèi)存采盒,必須用construct構(gòu)造對象,使用位構(gòu)造的內(nèi)存舅踪,行為未定義纽甘。
使用完,必須對每個構(gòu)造的元素調(diào)用destroy來銷毀抽碌,destroy接受一個指針悍赢,對指向的對象執(zhí)行析構(gòu)函數(shù)。
銷毀后货徙,可重新使用這部分內(nèi)存保存其他 string左权, 也可以釋放內(nèi)存還給系統(tǒng) -
拷貝和填充未初始化內(nèi)存的算法
拷貝和填充未初始化內(nèi)存的算法
vector<int> vi{1, 2, 3};
allocator<int> alloc;
auto p = alloc.allocate(vi.size() * 2); // 分配比 vi 中元素所占空間大一倍的動態(tài)內(nèi)存
auto q = alloc.unintialized_copy(vi.begin(), vi.end(), p); //拷貝vi中元素構(gòu)造從p開始的元素
uninitialized_fill_n(q, vi.size(), 42); // 將剩余元素初始化為42