[C++ Primer Note11] 動態(tài)內(nèi)存

除了自動和static對象外憔披,C++還支持動態(tài)分配對象等限。動態(tài)分配的對象的生存期與它們在哪里創(chuàng)建是無關(guān)的,只有當(dāng)顯式地被釋放時,這些對象才會銷毀精刷。

  1. 靜態(tài)內(nèi)存用來保存局部static對象,類static數(shù)據(jù)成員蔗候,以及定義在任何函數(shù)之外的變量怒允。棧內(nèi)存用來保存定義在函數(shù)內(nèi)的非static對象。分配在靜態(tài)內(nèi)存或棧內(nèi)存的對象由編譯器自動創(chuàng)建和銷毀锈遥。
  2. 除了靜態(tài)內(nèi)存和棧內(nèi)存纫事,每個程序還擁有一個內(nèi)存池。這部分內(nèi)存被稱作自由空間(free store)堆(heap)所灸。程序用堆來存儲動態(tài)分配的對象——即程序運(yùn)行時分配的對象丽惶。動態(tài)對象的生存期由程序而不是編譯器來控制。
  3. C++中爬立,動態(tài)內(nèi)存的管理是通過一對運(yùn)算符來完成的:new钾唬,在動態(tài)內(nèi)存中為對象分配空間并返回一個指向該對象的指針;delete侠驯,接受一個動態(tài)對象的指針抡秆,銷毀該對象,并釋放與之關(guān)聯(lián)的內(nèi)存吟策。
  4. 為了更容易儒士,更安全地使用動態(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頭文件中。
  5. 類似vector沪猴,智能指針也是模板辐啄,因此當(dāng)我們創(chuàng)建智能指針時,必須提供額外信息——指針可以指向的類型运嗜。
shared_ptr<string> p1;
shared_ptr<list<int>> p2;

默認(rèn)初始化的智能指針保存著一個空指針壶辜。
智能指針的使用方式與普通指針類似,解引用返回它指向的對象担租,如果在條件判斷中使用智能指針砸民,效果就是檢測它是否為空

image

  1. 最安全的分配和使用動態(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)行值初始化姿染。

  1. 當(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ù)器就會遞減寄锐。

  1. 當(dāng)指向一個對象的最后一個shared_ptr被銷毀時离熏,shared_ptr類會自動銷毀此對象杀餐。它是通過析構(gòu)函數(shù)實(shí)現(xiàn)的。
  2. 程序使用動態(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)了資源的對象間共享躁绸。

  1. C++定義了兩個運(yùn)算符newdelete來分配和釋放動態(tài)內(nèi)存吮铭。相對于智能指針,使用這兩個運(yùn)算符管理內(nèi)存非常容易出錯椎扬。
  2. 在自由空間分配的內(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)置類型龄寞,值初始化意味著對象有良好定義的值汰规。

  1. 用new分配const對象是合法的,一個動態(tài)const對象必須進(jìn)行初始化
const int *pci=new const int(1024);
  1. 一旦一個程序用光了它所有可用的內(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中钟病。

  1. 為了防止內(nèi)存耗盡,我們通過delete 表達(dá)式來將動態(tài)內(nèi)存歸還給系統(tǒng)刚梭。delete接受一個指針,指向我們想要釋放的對象:
delete p;  //p必須指向一個動態(tài)分配的對象或是一個空指針
  1. 釋放一塊并非new的內(nèi)存票唆,或者將相同的指針釋放多次朴读,其行為是未定義的。
  2. 由內(nèi)置指針管理的動態(tài)內(nèi)存在被顯式釋放前會一直存在走趋,如果不注意這一點(diǎn)很容易造成內(nèi)存泄漏衅金。
  3. 當(dāng)我們delete一個指針后,指針值就變?yōu)闊o效了簿煌,該指針就變成了所謂的空懸指針(dangling pointer)氮唯,即指向一塊曾經(jīng)保存數(shù)據(jù)對象但現(xiàn)在已經(jīng)無效的內(nèi)存的指針。如果我們需要保留指針姨伟,可以在delete之后賦予它nullptr的字面量惩琉。但實(shí)際上,可能有多個指針指向相同的內(nèi)存夺荒,這僅僅提供了很有限的保護(hù)瞒渠。
  4. 如前所述,我們?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使得智能指針可以綁定到一個指向其他類型資源的指針上榄笙。

image

  1. 當(dāng)將一個shared_ptr綁定到一個普通指針時晰搀,我們就將內(nèi)存的管理責(zé)任交給了這個shared_ptr。一旦這么做了办斑,就不應(yīng)該再使用內(nèi)置指針來訪問shared_ptr所指向的內(nèi)存了外恕。
  2. 如果使用智能指針杆逗,即使程序塊過早結(jié)束,智能指針類也能確保在內(nèi)存不再需要時將其釋放鳞疲。而直接管理的內(nèi)存如果在new之后delete之前發(fā)生了異常罪郊,則內(nèi)存不會釋放
  3. 我們還可以利用智能指針來管理不具有良好定義的析構(gòu)函數(shù)的類尚洽。
  4. 為了正確使用智能指針悔橄,我們必須堅(jiān)持一些基本規(guī)范
  • 不使用相同的內(nèi)置指針值初始化多個智能指針
  • 不delete get()返回的指針
  • 不使用get()初始化另一個智能指針
  • 如果你使用get()返回的指針,記住當(dāng)最后一個對應(yīng)的智能指針銷毀后腺毫,你的指針就變?yōu)闊o效了
  • 如果你使用智能指針管理的資源不是new分配的內(nèi)存癣疟,記住傳遞給它一個刪除器
  1. 一個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ú)占它指向的對象淤击,因此不支持普通的拷貝賦值操作。

image

  1. 雖然不能拷貝或賦值unique_ptr故源,但是可以通過releasereset將指針的所有權(quán)轉(zhuǎn)移:
p2.reset(p3.release());
  1. 不能拷貝unique_ptr的規(guī)則有一個例外:我們可以拷貝或賦值一個將要被銷毀的unique_ptr污抬。比如,從函數(shù)返回一個unique_ptr
  2. 與shared_ptr一樣绳军,我們可以重載一個unique_ptr中默認(rèn)的刪除器壕吹,此處不贅述
  3. weak_ptr是一種不控制所指向?qū)ο笊嫫诘闹悄苤羔槪赶蛴梢粋€shared_ptr管理的對象删铃。將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數(shù)耳贬。一旦最后一個指向?qū)ο蟮膕hared_ptr被銷毀,對象就會被釋放猎唁,即使有weak_ptr指向?qū)ο蟆?br>
    image
  4. 當(dāng)我們創(chuàng)建一個weak_ptr時咒劲,要用一個shared_ptr來初始化它:
auto p=make_shared<int>(42);
weak_ptr<int> wp(pP;
  1. 由于對象可能不存在,我們不能使用weak_ptr直接訪問對象诫隅,而必須調(diào)用lock腐魂,此函數(shù)檢查weak_ptr指向的對象是否仍存在。如果存在逐纬,則返回一個shared_ptr蛔屹。
  2. C++和標(biāo)準(zhǔn)庫提供了兩種一次分配一個對象數(shù)組的方法。C++定義了另一種new表達(dá)式語法豁生,可以分配并初始化一個對象數(shù)組兔毒。標(biāo)準(zhǔn)庫中包含一個名為allocator的類漫贞,允許我們將分配和初始化分離。
  3. 大多數(shù)應(yīng)用應(yīng)該使用標(biāo)準(zhǔn)庫容器而不是動態(tài)分配的數(shù)組育叁。使用容器更為簡單迅脐,更不容易出現(xiàn)內(nèi)存管理錯誤并且可能有更好的性能。
  4. 為了讓new分配一個對象數(shù)組豪嗽,我們要在類型名之后跟一對方括號谴蔑,在其中指明要分配的對象的數(shù)目,new返回指向第一個對象的指針
int *p=new int[size];
  1. 雖然我們通常稱new T[]分配的內(nèi)存為“動態(tài)數(shù)組”龟梦,但實(shí)際上我們只是得到一個數(shù)組元素類型的指針隐锭。動態(tài)數(shù)組并不是數(shù)組類型,因此前文所述的begin和end函數(shù)计贰,以及范圍for語句钦睡,通通不適用
  2. 默認(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};
  1. 可以用任意表達(dá)式來確定要分配的對象的數(shù)目:
size_t n=get_size();
int *p=new int[n];
  1. 當(dāng)我們用new分配一個大小為0的數(shù)組福贞,new返回一個合法的非空指針。但此指針不能解引用停士。
  2. 為了釋放動態(tài)數(shù)組挖帘,我們使用一種特殊形式的delete——指針前加上一個空括號對
delete [ ] p;

數(shù)組中的元素按逆序銷毀。如果沒有方括號恋技,行為是未定義的拇舀。

  1. 標(biāo)準(zhǔn)庫提供了一個可以管理new分配的數(shù)組的unique_ptr版本,為了用一個unique_ptr管理動態(tài)數(shù)組蜻底,我們必須在對象類型后面跟一對空方括號
unique_ptr<int[]> up(new int[10]);
up.release();    //自動用delete[]銷毀其指針
image
  1. shared_ptr不直接支持管理動態(tài)數(shù)組骄崩,必須提供自己定義的刪除器,此處不展開薄辅。
  2. 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)存
  1. 為了使用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)
  2. 拷貝和填充未初始化內(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市痴颊,隨后出現(xiàn)的幾起案子赏迟,更是在濱河造成了極大的恐慌,老刑警劉巖蠢棱,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锌杀,死亡現(xiàn)場離奇詭異,居然都是意外死亡泻仙,警方通過查閱死者的電腦和手機(jī)糕再,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玉转,“玉大人突想,你說我怎么就攤上這事【孔ィ” “怎么了猾担?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長刺下。 經(jīng)常有香客問我绑嘹,道長,這世上最難降的妖魔是什么橘茉? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任圾叼,我火速辦了婚禮,結(jié)果婚禮上捺癞,老公的妹妹穿的比我還像新娘夷蚊。我一直安慰自己,他們只是感情好髓介,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布惕鼓。 她就那樣靜靜地躺著,像睡著了一般唐础。 火紅的嫁衣襯著肌膚如雪箱歧。 梳的紋絲不亂的頭發(fā)上矾飞,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機(jī)與錄音呀邢,去河邊找鬼洒沦。 笑死,一個胖子當(dāng)著我的面吹牛价淌,可吹牛的內(nèi)容都是我干的申眼。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼蝉衣,長吁一口氣:“原來是場噩夢啊……” “哼括尸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起病毡,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤濒翻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后啦膜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體有送,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年僧家,在試婚紗的時候發(fā)現(xiàn)自己被綠了雀摘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡啸臀,死狀恐怖届宠,靈堂內(nèi)的尸體忽然破棺而出烁落,到底是詐尸還是另有隱情乘粒,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布伤塌,位于F島的核電站灯萍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏每聪。R本人自食惡果不足惜旦棉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望药薯。 院中可真熱鬧绑洛,春花似錦、人聲如沸童本。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽穷娱。三九已至绑蔫,卻和暖如春运沦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背配深。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工携添, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人篓叶。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓烈掠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親澜共。 傳聞我的和親對象是個殘疾皇子向叉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容