2.5w字长文爆肝 C++动态内存与智能指针一篇搞懂!太顶了!!!
動(dòng)態(tài)內(nèi)存與智能指針
- 1.動(dòng)態(tài)內(nèi)存與智能指針
- 2.shared_ptr類
- 2.1.make_shared函數(shù)
- 2.2.shared_ptr的拷貝和賦值
- 2.3.shared_ptr自動(dòng)銷毀所管理的對象
- 2.4. shared_ptr會(huì)自動(dòng)釋放相關(guān)聯(lián)的內(nèi)存
- 2.5.使用了動(dòng)態(tài)生存期的資源的類
- 2.6.定義StrBlob類
- 2.7. StrBlob構(gòu)造函數(shù)
- 2.8.元素訪問成員函數(shù)
- 2.9.StrBlob的拷貝,賦值和銷毀
- 2.9.5StrBlob類的測試
- 3.直接管理內(nèi)存
- 3.1.使用new動(dòng)態(tài)分配和初始化對象
- 3.2.動(dòng)態(tài)分配的const對象
- 3.3.釋放動(dòng)態(tài)內(nèi)存
- 3.4.指針指和delete
- 3.5.動(dòng)態(tài)對象的生存期直到被釋放時(shí)為止
- 3.6.小心:動(dòng)態(tài)內(nèi)存的管理非常容易出錯(cuò)
- 3.7.delete之后重置指針值,這只是提供了有限的保護(hù)
- 4.shared_ptr和new結(jié)合使用
- 4.1.不要混合使用普通指針和智能指針
- 4.2.不要使用get初始化另一個(gè)智能指針或?yàn)橹悄苤羔樫x值
- 4.3.shared_ptr指針操作:reset
- 4.4.shared_ptr的刪除器
- 5.智能指針和異常
- 5.1注意:智能指針陷阱
- 6.unique_ptr
- 6.1.傳遞unique_ptr參數(shù)和返回
- 6.2.unique_ptr的刪除器
- 7.weak_ptr
- 7.1.核查指針類
- 7.2.指針操作
- 7.3.StrBlobPtr類的測試
- 7.4.小插曲:改進(jìn)StrBlobPtr的構(gòu)造函數(shù)
本篇參考于《C++Primer》
三種內(nèi)存區(qū)別:
主要存放static靜態(tài)變量、全局變量、常量。這些數(shù)據(jù)內(nèi)存在編譯的時(shí)候就已經(jīng)為他們分配好了內(nèi)存,生命周期是整個(gè)程序從運(yùn)行到結(jié)束。
存放局部變量。在執(zhí)行函數(shù)的時(shí)候(包括main這樣的函數(shù)),函數(shù)內(nèi)的局部變量的存儲(chǔ)單元會(huì)在棧上創(chuàng)建,函數(shù)執(zhí)行完自動(dòng)釋放,生命周期是從該函數(shù)的開始執(zhí)行到結(jié)束。線性結(jié)構(gòu)。
程序員自己申請的任意大小的內(nèi)存。一直存在直到被釋放。鏈表結(jié)構(gòu)。
1.動(dòng)態(tài)內(nèi)存與智能指針
在C++中,動(dòng)態(tài)內(nèi)存的管理是通過一對運(yùn)算符來完成的。
動(dòng)態(tài)內(nèi)存的使用很容易出問題,因?yàn)榇_保在正確的時(shí)間釋放內(nèi)存是極其困難的。有時(shí)我們會(huì)忘記釋放內(nèi)存,在這種情況下就會(huì)產(chǎn)生內(nèi)存泄漏;有時(shí)在尚有指針引用內(nèi)存的情況下我們就釋放了它,在這種情況下就會(huì)產(chǎn)生引用非法內(nèi)存的指針。
為了更容易(同時(shí)也更安全)地使用動(dòng)態(tài)內(nèi)存,新的標(biāo)準(zhǔn)庫提供了兩種智能指針(smart pointer)類型來管理動(dòng)態(tài)對象。智能指針的行為類似常規(guī)指針,重要的區(qū)別是它負(fù)責(zé)自動(dòng)釋放所指向的對象。新標(biāo)準(zhǔn)庫提供的這兩種智能指針的區(qū)別在于管理底層指針的方式:
這三種類型都定義在memory頭文件中。
2.shared_ptr類
類似vector,智能指針也是模板,與vector一樣,我們在尖括號(hào)內(nèi)給出類型,之后是所定義的這種智能指針的名字:
shared_ptr<string>p1;//shared_ptr,可以指向string shared_ptr<list<int>>p2;//shared_ptr,可以指向int的list默認(rèn)初始化的智能指針中保存著一個(gè)空指針,后面我們將介紹初始化智能指針的其他方法。
智能指針的使用方式與普通指針類似。解引用一個(gè)智能指針返回它指向的對象。如果在一個(gè)條件判斷中使用智能指針,效果就是檢測它是否為空。
下面列出shared_ptr和unique_ptr所支持的操作。
圖片來源于《C++Primer》
2.1.make_shared函數(shù)
最安全的分配和使用動(dòng)態(tài)內(nèi)存的方法是調(diào)用一個(gè)名為make_shared的標(biāo)準(zhǔn)庫函數(shù)。此函數(shù)在動(dòng)態(tài)內(nèi)存中分配一個(gè)對象并初始化它,返回指向此對象的shared_ptr。與智能指針一樣,make_shared也定義在頭文件memory中。
當(dāng)要用make_shared時(shí),必須指定想要?jiǎng)?chuàng)建的對象的類型。定義方式與模板類相同,在函數(shù)名之后跟一個(gè)尖括號(hào),在其中給出類型:
我們通常用auto定義一個(gè)對象來保存make_shared的結(jié)果,這種方式比較簡單:
//p6指向一個(gè)動(dòng)態(tài)分配的空vector<string> auto p6 = make_shared<vector<string>>();2.2.shared_ptr的拷貝和賦值
當(dāng)進(jìn)行拷貝或賦值操作時(shí),每個(gè)shared_ptr都會(huì)記錄有多少個(gè)其他shared_ptr指向相同的對象:
auto p = make_shared<int>(42);//p指向的對象只有p一個(gè)引用者 auto q(p);//p和q指向相同對象,此對象有兩個(gè)引用者我們可以認(rèn)為每個(gè)shared_ptr都有一個(gè)關(guān)聯(lián)的計(jì)數(shù)器,通常稱其為引用計(jì)數(shù)。無論何時(shí)我們拷貝一個(gè)shared_ptr,計(jì)數(shù)器都會(huì)遞增。例如,當(dāng)用一個(gè)shared_ptr初始化另一個(gè)shared_ptr,或?qū)⑺鳛閰?shù)傳遞給一個(gè)函數(shù)以及作為函數(shù)的返回值時(shí),它所關(guān)聯(lián)的計(jì)數(shù)器就會(huì)遞增。當(dāng)我們給shared_ptr賦予一個(gè)新值或是shared_ptr被銷毀(例如一個(gè)局部的shared_ptr離開其作用域時(shí),計(jì)數(shù)器就會(huì)遞減。
一旦一個(gè)shared_ptr的計(jì)數(shù)器變?yōu)?,它就會(huì)自動(dòng)釋放自己所管理的對象:
此例中我們分配了一個(gè)int,將其指針保存在r中。接下來,我們將一個(gè)新值賦予r。在此情況下,r是唯一指向此int的shared_ptr,在把q賦給r的過程中,此int被自動(dòng)釋放。
2.3.shared_ptr自動(dòng)銷毀所管理的對象
當(dāng)指向一個(gè)對象的最后一個(gè)shared_ptr被銷毀時(shí),shared_ptr類會(huì)自動(dòng)銷毀此對象。它是通過另一個(gè)特殊的成員函數(shù)——析構(gòu)函數(shù)完成銷毀工作的。
shared_ptr的析構(gòu)函數(shù)會(huì)遞減它所指向的對象的引用計(jì)數(shù)。如果引用計(jì)數(shù)變?yōu)?,shared_ptr的析構(gòu)函數(shù)就會(huì)銷毀對象,并釋放它占用的內(nèi)存。
2.4. shared_ptr會(huì)自動(dòng)釋放相關(guān)聯(lián)的內(nèi)存
當(dāng)動(dòng)態(tài)對象不再被使用時(shí),shared_ptr類會(huì)自動(dòng)釋放動(dòng)態(tài)對象,這一特性使得動(dòng)態(tài)內(nèi)存的使用變得非常容易。
2.5.使用了動(dòng)態(tài)生存期的資源的類
程序使用動(dòng)態(tài)內(nèi)存出于以下三種原因之一:
1.程序不知道自己需要使用多少對象
2.程序不知道所需對象的準(zhǔn)確類型
3.程序需要在多個(gè)對象間共享數(shù)據(jù)
容器類是出于第一種原因而使用動(dòng)態(tài)內(nèi)存的典型例子,這里我們拿vector容器舉例,每個(gè)vector“擁有”其自己的元素。當(dāng)我們拷貝一個(gè)vector時(shí),原vector和副本vector中的元素是相互分離的:
vector<string>v1;//空vector {//新作用域vector<string>v2 = {"a","an","the"};v1 = v2;//從v2拷貝元素到v1中 }//v2被銷毀,其中的元素也被銷毀 //v1有三個(gè)元素,是原來v2中元素的拷貝由一個(gè)vector分配的元素只有當(dāng)這個(gè)vector存在時(shí)才存在。當(dāng)一個(gè)vector被銷毀時(shí),這個(gè)vector中的元素也都被銷毀。
出于第二種原因而使用動(dòng)態(tài)內(nèi)存的情況我們在這里不介紹。
我們現(xiàn)在來定義一個(gè)名為Blob的類,保存一組元素。與容器不同,我們希望Blob對象的不同拷貝之間共享相同的元素。即,當(dāng)我們拷貝一個(gè)Blob時(shí),原Blob對象及其拷貝應(yīng)該引用相同的底層元素。
一般而言,如果兩個(gè)對象共享底層的數(shù)據(jù),當(dāng)某個(gè)對象被銷毀時(shí),我們不能單方面地銷毀底層數(shù)據(jù):
在此例中,b1和b2共享相同的元素。當(dāng)b2離開作用域時(shí),這些元素必須保留,因?yàn)閎1仍然在使用它們。
note:
使用動(dòng)態(tài)內(nèi)存的一個(gè)常見原因是允許多個(gè)對象共享相同的狀態(tài)。
2.6.定義StrBlob類
現(xiàn)在讓我們來編寫在多個(gè)對象間共享數(shù)據(jù)的類,在這里我們不用模板來實(shí)現(xiàn)(因?yàn)槲疫€不會(huì)模板),因此,現(xiàn)在我們先定義一個(gè)管理string的類,此版本命名為StrBlob。
實(shí)現(xiàn)一個(gè)新的集合類型的最簡單方法是使用某個(gè)標(biāo)準(zhǔn)庫容器來管理元素。采用這種方法,我們可以借助標(biāo)準(zhǔn)庫類型來管理元素所使用的內(nèi)存空間。在本例中,我們將使用vector來保存元素。
但是,我們不能在一個(gè)Blob對象內(nèi)直接保存vector,因?yàn)橐粋€(gè)對象的成員在對象銷毀時(shí)也會(huì)被銷毀。例如,假定b1和b2是兩個(gè)Blob對象,共享相同的vector。如果此vector保存在其中一個(gè)Blob中——例如b2中,那么當(dāng)b2離開作用域時(shí),此vector也將被銷毀,也就是說其中的元素都將不復(fù)存在。為了保證vector中的元素繼續(xù)存在,我們將vector保存在動(dòng)態(tài)內(nèi)存中。
為了實(shí)現(xiàn)我們所希望的數(shù)據(jù)共享,我們?yōu)槊總€(gè)StrBlob設(shè)置一個(gè)shared_ptr來管理動(dòng)態(tài)分配的vector。此shared_ptr的成員將記錄有多少個(gè)StrBlob共享相同的vector,并在vector的最后一個(gè)使用者被銷毀時(shí)釋放vector。
我們還需要確定這個(gè)類應(yīng)該提供什么操作。當(dāng)前,我們將實(shí)現(xiàn)一個(gè)vector操作的小的子集。我們會(huì)修改訪問元素的操作(如front和back):在我們的類中,如果用戶試圖訪問不存在的元素,這些操作會(huì)拋出一個(gè)異常。
我們的類有一個(gè)默認(rèn)構(gòu)造函數(shù)和一個(gè)構(gòu)造函數(shù),接受單一的initializer_list類型參數(shù)。此構(gòu)造函數(shù)可以接受一個(gè)初始化器的花括號(hào)列表。
代碼如下:
class StrBlob{public:typedef std::vector<std::string>::size_type size_type;StrBlob();StrBlob(std::initializer_list<std::string>il);size_type size()const{return data->size();}bool empty()const {return data->empty();}void push_back(const std::string &t){data->push_back(t);}void pop_back();std::string&front();std::string&back();private:std::shared_ptr<std::vector<std::string>>data;void check(size_type i,const std::string &msg)const; };在此類中,我們實(shí)現(xiàn)了size、empty和push_back成員。這些成員通過指向底層vector的data成員來完成它們的工作。例如,對一個(gè)StrBlob對象調(diào)用size()會(huì)調(diào)用data->size(),依此類推。
2.7. StrBlob構(gòu)造函數(shù)
兩個(gè)構(gòu)造函數(shù)都使用初始化列表來初始化其data成員,令它指向一個(gè)動(dòng)態(tài)分配的vector。默認(rèn)構(gòu)造函數(shù)分配一個(gè)空vector:
StrBlob::StrBlob():data(make_shared<vector<string>>()){} StrBlob::StrBlob(initializer_list<string>il):data(make_shared<vector<string>>(il)){}接受一個(gè)initializer_list的構(gòu)造函數(shù)將其參數(shù)傳遞給對應(yīng)的vector構(gòu)造函數(shù)。此構(gòu)造函數(shù)通過拷貝列表中的值來初始化vector的元素。
2.8.元素訪問成員函數(shù)
pop_back、front和back操作訪問vector中的元素。這些操作在試圖訪問元素之前必須檢查元素是否存在。由于這些成員函數(shù)需要做相同的檢查操作,我們?yōu)镾trBlob定義了一個(gè)名為check的private工具函數(shù),它檢查一個(gè)給定索引是否在合法范圍內(nèi)。除了索引,check還接受一個(gè)string參數(shù),它會(huì)將此參數(shù)傳遞給異常處理程序,這個(gè)string描述了錯(cuò)誤內(nèi)容:
pop_back和元素訪問成員函數(shù)首先調(diào)用check。如果check成功,這些成員函數(shù)繼續(xù)利用底層vector的操作來完成自己的工作:
void StrBlob::check(size_type i,const string &msg) const {if (i >= data->size())throw out_of_range(msg); }pop_back和元素訪問成員函數(shù)首先調(diào)用check。如果check成功,這些成員函數(shù)繼續(xù)利用底層vector的操作來完成自己的工作:
string &StrBlob::front() {check(0,"front on empty StrBlob");return data->front(); }string &StrBlob::back() {check(0,"back on empty StrBlob");return data->back(); }void StrBlob::pop_back() {check(0,"pop_back on empty StrBlob");data->pop_back(); }front和back應(yīng)該對const進(jìn)行重載
2.9.StrBlob的拷貝,賦值和銷毀
StrBlob使用默認(rèn)版本的拷貝、賦值和銷毀成員函數(shù)來對此類型的對象進(jìn)行這些操作。默認(rèn)情況下,這些操作拷貝、賦值和銷毀類的數(shù)據(jù)成員。我們的StrBlob類只有一個(gè)數(shù)據(jù)成員,它是shared_ptr類型。因此,當(dāng)我們拷貝、賦值或銷毀一個(gè)StrBlob對象時(shí),它的shared_ptr成員會(huì)被拷貝、賦值或銷毀。
如前所見,拷貝一個(gè)shared_ptr會(huì)遞增其引用計(jì)數(shù);將一個(gè)shared_ptr賦予另一個(gè)shared_ptr會(huì)遞增賦值號(hào)右側(cè)shared_ptr的引用計(jì)數(shù),而遞減左側(cè)shared_ptr的引用計(jì)數(shù)。如果一個(gè)shared_ptr的引用計(jì)數(shù)變?yōu)?,它所指向的對象會(huì)被自動(dòng)銷毀。因此,對于由StrBlob構(gòu)造函數(shù)分配的vector,當(dāng)最后一個(gè)指向它的StrBlob對象被銷毀時(shí),它會(huì)隨之被自動(dòng)銷毀。
2.9.5StrBlob類的測試
完整代碼如下:
#ifndef MY_STRBLOB_H #define MY_STRBLOB_H #include <vector> #include <string> #include <initializer_list> #include <memory> #include <stdexcept>using namespace std;class StrBlob {public:typedef vector<string>::size_type size_type;StrBlob();StrBlob(initializer_list<string>il);size_type size()const {return data->size();}bool empty()const {return data->empty();}void push_back(const string &t) {data->push_back(t);}void pop_back();string &front();const string &front()const;string &back();const string &back()const;private:shared_ptr<std::vector<std::string>>data;void check(size_type i, const std::string &msg)const; };StrBlob::StrBlob(): data(make_shared<vector<string>>()) { }StrBlob::StrBlob(initializer_list<string>il): data(make_shared<vector<string>>(il)) {}void StrBlob::check(size_type i, const string &msg)const {if (i >= data->size())throw out_of_range(msg); }string &StrBlob::front() {check(0, "front on empty StrBlob");return data->front(); }const string &StrBlob::front()const {check(0, "front on empty StrBlob");return data->front(); }string &StrBlob::back() {check(0, "back on empty StrBlob");return data->back(); }const string &StrBlob::back()const {check(0, "back on empty StrBlob");return data->back(); }void StrBlob::pop_back() {check(0, "pop_back on empty StrBlob");data->pop_back(); }#endif測試代碼如下:
#include <iostream> using namespace std; #include "my_StrBlob.h"int main() {StrBlob b1;StrBlob b2 = {"a", "an", "the"};b1 = b2;b2.push_back("about");cout << b2.size() << endl;cout << b1.size() << endl;cout << b1.front() << " " << b1.back() << endl;const StrBlob b3 = b1;cout << b3.front() << " " << b3.back() << endl;return 0; }測試結(jié)果:
普通vector代碼如下:
測試結(jié)果:
3.直接管理內(nèi)存
C++定義了new分配內(nèi)存,delete釋放new分配的內(nèi)存。
相對于智能指針,使用這兩個(gè)運(yùn)算符管理內(nèi)存非常容易出錯(cuò)
而且,自己直接管理內(nèi)存的類與使用智能指針的類不同,它們不能依賴類對象拷貝、賦值和銷毀操作的任何默認(rèn)定義。因此,使用智能指針的程序更容易編寫和調(diào)試。
3.1.使用new動(dòng)態(tài)分配和初始化對象
在自由空間分配的內(nèi)存是無名的,因此new無法為其分配的對象命名,而是返回一個(gè)指向該對象的指針:
int *pi = new int;//pi指向一個(gè)動(dòng)態(tài)分配的,未初始化的無名對象此new表達(dá)式在自由空間構(gòu)造一個(gè)int型對象,并返回指向該對象的指針。
默認(rèn)情況下,動(dòng)態(tài)分配的對象是默認(rèn)初始化的,這意味著內(nèi)置類型或組合類型的對象的值將是未定義的,而類類型對象將用默認(rèn)構(gòu)造函數(shù)進(jìn)行初始化:
string *ps = new string;//初始化空string int *pi = new int;//pi指向一個(gè)未初始化的int初始化方式(1.使用圓括號(hào),2.使用列表初始化,3.在類型名之后跟一對空括號(hào)進(jìn)行值初始化):
int *pi = new int(1024);//pi指向的對象的值為1024 string *ps = new string(10,'9');//*ps為"9999999999" vector<int>*pv = new vector<int>{0,1,2,3,4,5,6,7,8,9}; //vector有10個(gè)元素,值依次從0到9string *ps1 = new string;//默認(rèn)初始化為空string string *ps = new string();//值初始化為空string int *pi1 = new int;//默認(rèn)初始化;*pi1的值未定義 int *pi2 = new int();//值初始化為0,*pi2為0如果我們用auto來初始化,由于編譯器要用初始化器的類型來推斷要分配的類型,只有當(dāng)括號(hào)中僅有單一初始化器時(shí)才可以使用auto:
auto p1 = new auto(obj);//p指向一個(gè)與obj類型相同的對象//該對象用obj進(jìn)行初始化 auto p2 = new auto{a,b,c};//錯(cuò)誤;括號(hào)中只能有單個(gè)初始化器p1的類型是一個(gè)指針,指向從obj自動(dòng)推斷出的類型。若obj是一個(gè)int,那么p1就是int*;若obj是一個(gè)string,那么p1是一個(gè)string*;依此類推。新分配的對象用obj的值進(jìn)行初始化。
3.2.動(dòng)態(tài)分配的const對象
用new分配const對象是合法的:
const int *pci = new const int(1024); //分配并初始化一個(gè)const int const string *pcs = new const string; //分配并默認(rèn)初始化一個(gè)const的空string類似其他任何const對象,一個(gè)動(dòng)態(tài)分配的const對象必須進(jìn)行初始化。對于一個(gè)定義了默認(rèn)構(gòu)造函數(shù)的類類型,其const動(dòng)態(tài)對象可以隱式初始化,而其他類型的對象就必須顯式初始化。由于分配的對象是const的,new返回的指針是一個(gè)指向const的指針。
3.3.釋放動(dòng)態(tài)內(nèi)存
為了防止內(nèi)存耗盡(在這里不介紹),在動(dòng)態(tài)內(nèi)存使用完畢后,必須將其歸還給系統(tǒng)。我們通過delete表達(dá)式來將動(dòng)態(tài)內(nèi)存歸還給系統(tǒng)。delete表達(dá)式接受一個(gè)指針,指向我們想要釋放的對象:
delete p;//p必須指向一個(gè)動(dòng)態(tài)分配的對象或是一個(gè)空指針note:
delete p; 之后,p的值(指向的地址不變),但不能再使用p處理該地址的內(nèi)容,也不能再delete p,因?yàn)閐elete之后,p不指向nullptr。
通常需要delete之后置指針為空:
delete p;
p = nullptr;
與new類型類似,delete表達(dá)式也執(zhí)行兩個(gè)動(dòng)作:銷毀給定的指針指向的對象;釋放對應(yīng)的內(nèi)存。
3.4.指針指和delete
我們傳遞給delete的指針必須指向動(dòng)態(tài)分配的內(nèi)存,或者是一個(gè)空指針。釋放一塊并非new分配的內(nèi)存,或者將相同的指針值釋放多次,又或者釋放一個(gè)局部變量等,其行為是未定義的。
舉個(gè)例子:
如果我們delete了局部變量,那么當(dāng)局部變量離開了它的作用域,系統(tǒng)要銷毀這個(gè)局部變量!可這個(gè)局部變量已經(jīng)被我們銷毀了,就相當(dāng)于將相同的指針值釋放多次。(如果錯(cuò)誤,還請指正)
雖然一個(gè)const對象的值不能被改變,但它本身是可以被銷毀的。
const int *pci = new const int(1024); delete pci;3.5.動(dòng)態(tài)對象的生存期直到被釋放時(shí)為止
注意:調(diào)用者必須記得釋放內(nèi)存
讓我們看下面這段代碼:
#include <iostream> using namespace std;int *fff(int a) //返回一個(gè)指針,指向一個(gè)動(dòng)態(tài)分配的對象 {return new int(a); }void use_fff(int a) {int *b = fff(a);//調(diào)用者必須記得釋放此內(nèi)存cout << *b << endl; }//b離開了它的作用域,但它所指向的內(nèi)存沒有被釋放!!!int main() {use_fff(42);return 0; }注意:
由內(nèi)置指針(而不是智能指針)管理的動(dòng)態(tài)內(nèi)存在被顯式釋放前一直都會(huì)存在。
當(dāng)然,如果在這段代碼中,我們不釋放內(nèi)存,那么目的就是,我們的系統(tǒng)中的其他代碼要使用use_fff所分配的對象,我們就應(yīng)該修改此函數(shù),讓它返回一個(gè)指針,指向它分配的內(nèi)存:
#include <iostream> using namespace std;int *fff(int a) { //返回一個(gè)指針,指向一個(gè)動(dòng)態(tài)分配的對象return new int(a); }int *use_fff(int a) {int *b = fff(a);//調(diào)用者必須記得釋放此內(nèi)存cout << *b << endl;return b; }int main() {int *c = use_fff(42);cout << *c << endl;return 0; }3.6.小心:動(dòng)態(tài)內(nèi)存的管理非常容易出錯(cuò)
堅(jiān)持只使用智能指針,就可以避免所有這些問題。對于一塊內(nèi)存,只有在沒有任何智能指針指向它的情況下,智能指針才會(huì)自動(dòng)釋放它。
3.7.delete之后重置指針值,這只是提供了有限的保護(hù)
當(dāng)我們delete一個(gè)指針后,指針值就變?yōu)闊o效了。雖然指針無效,但是很多機(jī)器上指針(已經(jīng)釋放了的)仍然保存著動(dòng)態(tài)內(nèi)存的地址。在delete之后,指針就變成了空懸指針
空懸指針:指向一塊曾經(jīng)保存數(shù)據(jù)對象但現(xiàn)在已經(jīng)無效的內(nèi)存的指針。
未初始化指針的所有缺點(diǎn)空懸指針也都有。有一種方法可以避免空懸指針的問題:在指針即將要離開其作用域之前釋放掉它所關(guān)聯(lián)的內(nèi)存。這樣,在指針關(guān)聯(lián)的內(nèi)存被釋放掉之后,就沒有機(jī)會(huì)繼續(xù)使用指針了。如果我們需要保留指針,可以在delete之后將nullptr賦予指針,這樣就清楚地指出指針不指向任何對象。
但這也僅僅提供了有限的保護(hù)。
比如下面這個(gè)例子:
int *p(new int(42));//p指向動(dòng)態(tài)內(nèi)存 auto q = p;//p和q指向相同內(nèi)存 delete p;//p和q均變?yōu)闊o效 p = nullptr;//指出p不再綁定到任何對象本例中p和q指向相同的動(dòng)態(tài)分配的對象。我們delete此內(nèi)存,然后將p置為nullptr,指出它不再指向任何對象。但是,重置p對q沒有任何作用,在我們釋放p所指向的(同時(shí)也是q所指向的!)內(nèi)存時(shí),q也變?yōu)闊o效了。在實(shí)際系統(tǒng)中,查找指向相同內(nèi)存的所有指針是異常困難的。(在這里,我們很難發(fā)現(xiàn)q已經(jīng)無效了)
4.shared_ptr和new結(jié)合使用
如果我們不初始化一個(gè)智能指針,它就會(huì)被初始化為一個(gè)空指針,如下面代碼所示,我們還可以用new返回的指針來初始化智能指針:
shared_ptr<double>p1;//shared_ptr可以指向一個(gè)double shared_ptr<int>p2(new int(42));//p2指向一個(gè)值為42的int接受指針參數(shù)的智能指針構(gòu)造函數(shù)是explicit的,因此,我們不能將一個(gè)內(nèi)置指針隱式轉(zhuǎn)換為一個(gè)智能指針,必須使用直接初始化形式來初始化一個(gè)智能指針:
shared_ptr<int>p1 = new int(1024);//錯(cuò)誤,必須使用直接初始化 shared_ptr<int>p2(new int(1024));//正確,直接初始化 shared_ptr<int>clone(int p) {return new int(p);//錯(cuò)誤;隱式轉(zhuǎn)換為shared_ptr<int>; }shared_ptr<int>clone(int p) {return shared_ptr<int>(new int(p));//正確;顯式地用int*創(chuàng)建shared_ptr<int>; }默認(rèn)情況下,
4.1.不要混合使用普通指針和智能指針
shared_ptr可以協(xié)調(diào)對象的析構(gòu),但這僅限于其自身的拷貝(也是shared_ptr)之間。這也是為什么我們推薦使用make_shared而不是new的原因。這樣,我們就能在分配對象的同時(shí)就將shared_ptr與之綁定,從而避免了無意中將同一塊內(nèi)存綁定到多個(gè)獨(dú)立創(chuàng)建的shared_ptr上。
現(xiàn)在讓我們看下面這段代碼:
//在函數(shù)調(diào)用時(shí)ptr被創(chuàng)建并初始化 void process(shared_ptr<int>ptr) {//使用ptr };//ptr離開作用域,被銷毀process的參數(shù)是傳值方式傳遞,因此實(shí)參會(huì)被拷貝到ptr,創(chuàng)建一個(gè)shared_ptr會(huì)遞增其引用計(jì)數(shù),拷貝一個(gè)shared_ptr也會(huì)遞增其引用計(jì)數(shù),因此在process運(yùn)行過程中,其引用計(jì)數(shù)至少為2,等ptr離開作用域,ptr的引用計(jì)數(shù)會(huì)遞減,但不會(huì)變?yōu)?。因此,當(dāng)局部變量ptr被銷毀時(shí),ptr指向的內(nèi)存不會(huì)被釋放。
使用process函數(shù)的正確方法是傳遞給它一個(gè)shared_ptr:
shared_ptr<int>p(new int(42));//引用計(jì)數(shù)為1 process(p);//拷貝p會(huì)遞增引用計(jì)數(shù),引用計(jì)數(shù)為2 int i = *p;//正確,引用計(jì)數(shù)為1現(xiàn)在讓我們混合使用普通指針和智能指針,看會(huì)發(fā)生什么???
雖然我們不能傳遞給process一個(gè)內(nèi)置指針,但可以傳遞給它一個(gè)(臨時(shí)的)shared_ptr,這個(gè)shared_ptr是用一個(gè)內(nèi)置指針顯式構(gòu)造的。但是,這樣做很可能會(huì)導(dǎo)致錯(cuò)誤:
int *x(new int(1024));//危險(xiǎn),x是一個(gè)普通指針,不是一個(gè)智能指針 process(x);//錯(cuò)誤,不能將int*轉(zhuǎn)換為一個(gè)shared_ptr<int> process(shared_ptr<int>(x));//合法的,但內(nèi)存會(huì)被釋放 int j = *x;//未定義,x是一個(gè)空懸指針在上面的調(diào)用中,我們將一個(gè)臨時(shí)shared_ptr傳遞給process。當(dāng)這個(gè)調(diào)用所在的表達(dá)式結(jié)束時(shí),這個(gè)臨時(shí)對象就被銷毀了。銷毀這個(gè)臨時(shí)變量會(huì)遞減引用計(jì)數(shù),此時(shí)引用計(jì)數(shù)就變?yōu)?了。因此,當(dāng)臨時(shí)對象被銷毀時(shí),它所指向的內(nèi)存會(huì)被釋放。
但x繼續(xù)指向(已經(jīng)釋放的)內(nèi)存,從而變成一個(gè)空懸指針。如果試圖使用x的值,其行為是未定義的。
當(dāng)將一個(gè)shared_ptr綁定到一個(gè)普通指針時(shí),我們就將內(nèi)存的管理責(zé)任交給了這個(gè)shared_ptr。一旦這樣做了,我們就不應(yīng)該再使用內(nèi)置指針來訪問shared_ptr所指向的內(nèi)存了。
注意:
使用一個(gè)內(nèi)置指針來訪問一個(gè)智能指針?biāo)?fù)責(zé)的對象是很危險(xiǎn)的,因?yàn)槲覀儫o法知道對象何時(shí)會(huì)被銷毀。
4.2.不要使用get初始化另一個(gè)智能指針或?yàn)橹悄苤羔樫x值
智能指針類型定義了一個(gè)名為get的函數(shù)(參見表12.1),它返回一個(gè)內(nèi)置指針,指向智能指針管理的對象。
當(dāng)我們需要向不能使用智能指針的代碼傳遞一個(gè)內(nèi)置指針時(shí),我們才會(huì)用get。
使用get返回的指針的代碼不能delete此指針。
雖然編譯器不會(huì)給出錯(cuò)誤信息,但將另一個(gè)智能指針也綁定到get返回的指針上是錯(cuò)誤的:
shared_ptr<int>p(new int(42));//引用計(jì)數(shù)為1 int *q = p.get();//正確,但使用q時(shí)要注意,不要讓它管理的指針被釋放 {//新程序塊 //未定義:兩個(gè)獨(dú)立的shared_ptr指向相同的內(nèi)存shared_ptr<int>q; }//程序塊結(jié)束,q被銷毀,它指向的內(nèi)存被釋放 int foo = *p;//未定義:p指向的內(nèi)存已經(jīng)被釋放了在本例中,p和q指向相同的內(nèi)存。由于它們是相互獨(dú)立創(chuàng)建的,因此各自的引用計(jì)數(shù)都是1。當(dāng)q所在的程序塊結(jié)束時(shí),q被銷毀,這會(huì)導(dǎo)致q指向的內(nèi)存被釋放。從而p變成一個(gè)空懸指針,意味著當(dāng)我們試圖使用p時(shí),將發(fā)生未定義的行為。而且,當(dāng)p被銷毀時(shí),這塊內(nèi)存會(huì)被第二次delete。
注意:
get用來將指針的訪問權(quán)限傳遞給代碼,你只有在確定代碼不會(huì)delete指針的情況下,才能使用get。特別是,永遠(yuǎn)不要用get初始化另一個(gè)智能指針或者為另一個(gè)智能指針賦值。
4.3.shared_ptr指針操作:reset
上面的代碼,指針p指向的內(nèi)存已經(jīng)被釋放了,不過我們可以用reset來將一個(gè)新的指針賦予一個(gè)shared_ptr:
p = new int(1024);//錯(cuò)誤,不能將一個(gè)指針賦予shared_ptr p.reset(new int(1024));//正確,p指向一個(gè)新對象與賦值類似,reset會(huì)更新引用計(jì)數(shù),如果需要的話,會(huì)釋放p指向的對象。**reset成員經(jīng)常與unique一起使用,來控制多個(gè)shared_ptr共享的對象。**在改變底層對象之前,我們檢查自己是否是當(dāng)前對象僅有的用戶。如果不是,在改變之前要制作一份新的拷貝:
if (!p.unique())p.reset(new string(*p));//我們不是唯一用戶;分配新的拷貝 *p+=newVal;//現(xiàn)在我們知道自己是唯一的用戶,可以改變對象的值4.4.shared_ptr的刪除器
本文暫時(shí)未介紹!!!
5.智能指針和異常
使用智能指針,即使程序塊過早結(jié)束或發(fā)生異常,智能指針類也能確保在內(nèi)存不再需要時(shí)將其釋放:
void f() {shared_ptr<int>sp(new int(42));//分配一個(gè)對象//發(fā)生異常,且未被f捕獲 }//在函數(shù)結(jié)束時(shí)shared_ptr自動(dòng)釋放內(nèi)存函數(shù)的退出有兩種可能,正常處理結(jié)束或者發(fā)生了異常,無論哪種情況,局部對象都會(huì)被銷毀。在上面的程序中,sp是一個(gè)shared_ptr,因此sp銷毀時(shí)會(huì)檢查引用計(jì)數(shù)。在此例中,sp是指向這塊內(nèi)存的唯一指針,因此內(nèi)存會(huì)被釋放掉。
與之相對的,當(dāng)發(fā)生異常時(shí),我們直接管理的內(nèi)存是不會(huì)自動(dòng)釋放的。如果使用內(nèi)置指針管理內(nèi)存,且在new之后在對應(yīng)的delete之前發(fā)生了異常,則內(nèi)存不會(huì)被釋放:
如果在new和delete之間發(fā)生異常,且異常未在f中被捕獲,則內(nèi)存就永遠(yuǎn)不會(huì)被釋放了。在函數(shù)f之外沒有指針指向這塊內(nèi)存,因此就無法釋放它了。
5.1注意:智能指針陷阱
智能指針可以提供對動(dòng)態(tài)分配的內(nèi)存安全而又方便的管理,但這建立在正確使用的前提下。為了正確使用智能指針,我們必須堅(jiān)持一些基本規(guī)范:
6.unique_ptr
一個(gè)unique_ptr“擁有”它所指向的對象。與shared_ptr不同,某個(gè)時(shí)刻只能有一個(gè)unique_ptr指向一個(gè)給定對象。當(dāng)unique_ptr被銷毀時(shí),它所指向的對象也被銷毀。表12.4列出了unique_ptr特有的操作。與shared_ptr相同的操作列在表12.1)中。
與shared_ptr不同,沒有類似make_shared的標(biāo)準(zhǔn)庫函數(shù)返回一個(gè)unique_ptr。當(dāng)我們定義一個(gè)unique_ptr時(shí),需要將其綁定到一個(gè)new返回的指針上。類似shared_ptr,初始化unique_ptr必須采用直接初始化形式:
unique_ptr<double>p1;//可以指向一個(gè)double的unique_ptr unique_ptr<int>p2(new int(42));//p2指向一個(gè)值為42的int由于一個(gè)unique_ptr擁有它指向的對象,因此unique_ptr不支持普通的拷貝或賦值操作:
unique_ptr<string>p1(new string("Stegosaurus")); unique_ptr<string>p2(p1);//錯(cuò)誤,unique_ptr不支持拷貝 unique_ptr<string>p3; p3 = p2;//錯(cuò)誤,unique不支持賦值雖然我們不能拷貝或賦值unique_ptr,但可以通過調(diào)用release或reset將指針的所有權(quán)從一個(gè)(非const)unique_ptr轉(zhuǎn)移給另一個(gè)unique:
//將所有權(quán)從p1(指向string Stegosaurus)轉(zhuǎn)移給p2 unique_ptr<string>p2(p1.release());//release將p1置為空 unique_ptr<string>p3(new string("Trex")); //將所有權(quán)從p3轉(zhuǎn)移給p2 p2.reset(p3.release());//reset釋放了p2原來指向的內(nèi)存因此,p2被初始化為p1原來保存的指針,而p1被置為空。
p2.reset(p3.release());因此,對p2調(diào)用reset釋放了用"Stegosaurus"初始化的string所使用的內(nèi)存,將p3對指針的所有權(quán)轉(zhuǎn)移給p2,并將p3置為空。
調(diào)用release會(huì)切斷unique_ptr和它原來管理的對象間的聯(lián)系。**release返回的指針通常被用來初始化另一個(gè)智能指針或給另一個(gè)智能指針賦值。**在本例中,管理內(nèi)存的責(zé)任簡單地從一個(gè)智能指針轉(zhuǎn)移給另一個(gè)。但是,如果我們不用另一個(gè)智能指針來保存release返回的指針,我們的程序就要負(fù)責(zé)資源的釋放:
p2.release();//錯(cuò)誤,p2不會(huì)釋放內(nèi)存,而且我們丟失了指針 auto p = p2.release();//正確,但我們必須記得delete(p)6.1.傳遞unique_ptr參數(shù)和返回
unique_ptr不能拷貝unique_ptr的規(guī)則有一個(gè)例外:我們可以拷貝或賦值一個(gè)將要被銷毀的unique_ptr。最常見的例子是從函數(shù)返回一個(gè)unique_ptr:
unique_ptr<int>clone(int p) {//正確,從int*創(chuàng)建一個(gè)unique_ptr<int>return unique_ptr<int>(new int(p)); }還可以返回一個(gè)局部對象的拷貝:
unique_ptr<int>clone(int p) {unique_ptr<int>ret(new int (p));//...return ret; }對于兩段代碼,編譯器都知道要返回的對象將要被銷毀。在此情況下,編譯器執(zhí)行一種特殊的“拷貝”(本文無介紹)
6.2.unique_ptr的刪除器
本文暫時(shí)不介紹!!!
7.weak_ptr
weak_ptr(見表12.5)是一種不控制所指向?qū)ο笊嫫诘闹悄苤羔?#xff0c;它指向由一個(gè)shared_ptr管理的對象。將一個(gè)weak_ptr綁定到一個(gè)shared_ptr不會(huì)改變shared_ptr的引用計(jì)數(shù)。一旦最后一個(gè)指向?qū)ο蟮膕hared_ptr被銷毀,對象就會(huì)被釋放。即使有weak_ptr指向?qū)ο?#xff0c;對象也還是會(huì)被釋放,因此,weak_ptr的名字抓住了這種智能指針“弱”共享對象的特點(diǎn)。
當(dāng)我們創(chuàng)建一個(gè)weak_ptr時(shí),要用一個(gè)shared_ptr來初始化它:
本例中wp和p指向相同的對象。由于是弱共享,創(chuàng)建wp不會(huì)改變p的引用計(jì)數(shù);wp指向的對象可能被釋放掉。
由于對象可能不存在,我們不能使用weak_ptr直接訪問對象,而必須調(diào)用lock。 此函數(shù)檢查weak_ptr指向的對象是否仍存在。如果存在,lock返回一個(gè)指向共享對象的shared_ptr。與任何其他shared_ptr類似,只要此shared_ptr存在,它所指向的底層對象也就會(huì)一直存在。例如:
在這段代碼中,只有當(dāng)lock調(diào)用返回true時(shí)我們才會(huì)進(jìn)入if語句體。在if中,使用np訪問共享對象是安全的。
7.1.核查指針類
作為weak_ptr用途的一個(gè)展示,我們將為StrBlob類定義一個(gè)伴隨指針類。我們的指針類將命名為StrBlobPtr,會(huì)保存一個(gè)weak_ptr,指向StrBlob的data成員,這是初始化時(shí)提供給它的。通過使用weak_ptr,不會(huì)影響一個(gè)給定的StrBlob所指向的vector的生存期。但是,可以阻止用戶訪問一個(gè)不再存在的vector的企圖。
StrBlobPtr會(huì)有兩個(gè)數(shù)據(jù)成員:wptr,或者為空,或者指向一個(gè)StrBlob中的vector;curr,保存當(dāng)前對象所表示的元素的下標(biāo)。類似它的伴隨類StrBlob,我們的指針類也有一個(gè)check成員來檢查解引用StrBlobPtr是否安全:
默認(rèn)構(gòu)造函數(shù)生成一個(gè)空的StrBlobPtr。其構(gòu)造函數(shù)初始化列表將curr顯式初始化為0,并將wptr隱式初始化為一個(gè)空weak_ptr。第二個(gè)構(gòu)造函數(shù)接受一個(gè)StrBlob引用和一個(gè)可選的索引值。此構(gòu)造函數(shù)初始化wptr,令其指向給定StrBlob對象的shared_ptr中的vector,并將curr初始化為sz的值。我們使用了默認(rèn)參數(shù),表示默認(rèn)情況下將curr初始化為第一個(gè)元素的下標(biāo)。我們將會(huì)看到,StrBlob的end成員將會(huì)用到參數(shù)sz。
值得注意的是,我們不能將StrBlobPtr綁定到一個(gè)const StrBlob對象。這個(gè)限制是由于構(gòu)造函數(shù)接受一個(gè)非const StrBlob對象的引用而導(dǎo)致的。
StrBlobPtr的check成員與StrBlob中的同名成員不同,它還要檢查指針指向的vector是否還存在:
由于一個(gè)weak_ptr不參與其對應(yīng)的shared_ptr的引用計(jì)數(shù),StrBlobPtr指向的vector可能已經(jīng)被釋放了。如果vector已銷毀,lock將返回一個(gè)空指針。在本例中,任何vector的引用都會(huì)失敗,于是拋出一個(gè)異常。否則,check會(huì)檢查給定索引,如果索引值合法,check返回從lock獲得的shared_ptr。
7.2.指針操作
在這里我們不用重載運(yùn)算符的方法,我們定義名為deref和incr的函數(shù),分別用來解引用和遞增StrBlobPtr。
deref成員調(diào)用check,檢查使用vector是否安全以及curr是否在合法范圍內(nèi):
如果check成功,p就是一個(gè)shared_ptr,指向StrBlobPtr所指向的vector。表達(dá)式(*p)[curr]解引用shared_ptr來獲得vector,然后使用下標(biāo)運(yùn)算符提取并返回curr位置上的元素。
//incr成員也調(diào)用check //前綴遞增;返回遞增后的對象的引用 StrBlobPtr&StrBlobPtr::incr() {//如果curr已經(jīng)指向容器的尾后位置,就不能遞增它check(curr,"increment past end of StrBlobPtr");++curr;//推進(jìn)當(dāng)前位置return *this; }當(dāng)然,為了訪問data成員,我們的指針類必須聲明為StrBlob的friend(參見7.3.4節(jié),第250頁)。我們還要為StrBlob類定義begin和end操作,返回一個(gè)指向它自身的StrBlobPtr:
//對于StrBlob中的友元聲明來說,此前置聲明是必要的 class StrBlobPtr; class StrBlob {friend class StrBlobPtr;//返回指向首元素和后元素的StrBlobPtrStrBlobPtr begin(){return StrBlobPtr (*this);}StrBlobPtr end(){auto ret = StrBlobPtr(*this,data->size());return ret;} };7.3.StrBlobPtr類的測試
完整代碼如下:
#ifndef MY_STRBLOB_H #define MY_STRBLOB_H #include <vector> #include <string> #include <initializer_list> #include <memory> #include <stdexcept>using namespace std;//提前聲明,StrBlob中的友類聲明所需 class StrBlobPtr;class StrBlob {friend class StrBlobPtr;public:typedef vector<string>::size_type size_type;StrBlob();StrBlob(initializer_list<string>il);size_type size()const {return data->size();}bool empty()const {return data->empty();}//添加和刪除元素void push_back(const string &t) {data->push_back(t);}void pop_back();//元素訪問string &front();const string &front()const;string &back();const string &back()const;//提供給StrBlobPtr的接口StrBlobPtr begin();//這是聲明,定義StrBlobPtr后才能定義這兩個(gè)函數(shù)StrBlobPtr end();private:shared_ptr<std::vector<std::string>>data;//如果data[i]不合法,拋出一個(gè)異常void check(size_type i, const std::string &msg)const; };inline StrBlob::StrBlob(): data(make_shared<vector<string>>()) {}StrBlob::StrBlob(initializer_list<string>il): data(make_shared<vector<string>>(il)) {}inline void StrBlob::check(size_type i, const string &msg) const {if (i >= data->size())throw out_of_range(msg); }inline string&StrBlob::front() {//如果vector為空,check會(huì)拋出一個(gè)異常check(0,"front on empty StrBlob");return data->front(); }//const版本front inline const string &StrBlob::front()const {check(0, "front on empty StrBlob");return data->front(); }inline string &StrBlob::back() {check(0, "back on empty StrBlob");return data->back(); }//const 版本 back inline const string &StrBlob::back()const {check(0, "back on empty StrBlob");return data->back(); }inline void StrBlob::pop_back() {check(0, "pop_back on empty StrBlob");data->pop_back(); }//當(dāng)試圖訪問一個(gè)不存在的元素時(shí),StrBlobPtr拋出一個(gè)異常 class StrBlobPtr {friend bool eq(const StrBlobPtr &, const StrBlobPtr &);public:StrBlobPtr(): curr(0) {}StrBlobPtr(StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) {}string &deref()const;StrBlobPtr &incr();//前綴遞增StrBlobPtr &decr();//前綴遞減private://若檢查成功,check返回一個(gè)指向vector的shared_ptrshared_ptr<vector<string>>check(size_t, const string &)const;//保存一個(gè)weak_ptr,意味著底層vector可能會(huì)被銷毀weak_ptr<vector<string>>wptr;size_t curr;//在數(shù)組中的當(dāng)前位置 };inline shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg)const {auto ret = wptr.lock();//vector還存在嗎?if (!ret)throw runtime_error("unbound StrBlobPtr");if (i >= ret->size())throw out_of_range(msg);return ret; }inline string &StrBlobPtr::deref()const {auto p = check(curr, "dereference past end");return (*p)[curr]; }//前綴遞增:返回遞增后的對象的引用 inline StrBlobPtr &StrBlobPtr::incr() {//如果curr已經(jīng)指向容器的尾后位置,就不能遞增它check(curr, "increment past end of StrBlobPtr");++curr;//推進(jìn)當(dāng)前位置return *this; }//前綴遞減:返回遞減后的對象的引用 inline StrBlobPtr &StrBlobPtr::decr() {//如果curr已經(jīng)為0,遞減它就會(huì)產(chǎn)生一個(gè)非法下標(biāo)--curr; //遞減當(dāng)前位置check(-1, "decrement past begin of StrBlobPtr");return *this; }//StrBlob的begin和end成員的定義 inline StrBlobPtr StrBlob::begin() {return StrBlobPtr(*this); }inline StrBlobPtr StrBlob::end() {auto ret = StrBlobPtr(*this, data->size());return ret; }//StrBlobPtr的比較操作 inline bool eq(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {auto l = lhs.wptr.lock(), r = rhs.wptr.lock();//若底層的vector是同一個(gè)if (l == r)//則兩個(gè)指針都是空,或者指向相同的元素時(shí),它們相等return (!r || lhs.curr == rhs.curr);elsereturn false;//若指向不同vector,則不可能相等 }inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {return !eq(lhs, rhs); } #endif主程序代碼:
#include <iostream> using namespace std; #include "my_StrBlob.h"int main(int argc, char **argv) {StrBlob b1;{StrBlob b2 = {"a", "an", "the"};b1 = b2;b2.push_back("about");cout << b2.size() << endl;}cout << b1.size() << endl;cout << b1.front() << " " << b1.back() << endl;const StrBlob b3 = b1;cout << b3.front() << " "<<b3.back() << endl;for (auto it = b1.begin(); neq(it, b1.end()); it.incr())cout << it.deref() << endl;return 0; }測試結(jié)果:
這次我們用文件讀入的方式測試:
代碼如下:
#include <iostream> #include <fstream> using namespace std;#include "my_StrBlob.h"int main(int argc, char **argv) {ifstream in(argv[1]);if (!in) {cout << "無法打開輸入文件" << endl;return -1;}StrBlob b;string s;while (getline(in, s))b.push_back(s);for (auto it = b.begin(); neq(it, b.end()); it.incr())cout << it.deref() << endl;system("pause");return 0; }7.4.小插曲:改進(jìn)StrBlobPtr的構(gòu)造函數(shù)
我們不能將StrBlobPtr綁定到一個(gè)const StrBlob對象。這個(gè)限制是由于構(gòu)造函數(shù)接受一個(gè)非const StrBlob對象的引用而導(dǎo)致的
現(xiàn)在我們來設(shè)計(jì)一個(gè)可以接受const StrBlob對象的引用的構(gòu)造函數(shù)。
首先,為StrBlobPtr定義能接受const StrBlob &參數(shù)的構(gòu)造函數(shù):
StrBlob(const StrBlob &a,size_t sz = 0):wptr(a.data),curr(sz){}其次,為StrBlob定義能操作const對象的begin和end。
聲明:
StrBlobPtr begin() const; StrBlobPtr end() const;定義:
inline StrBlobPtr StrBlob::begin()const {return StrBlobPtr(*this); }inline StrBlobPtr StrBlob::end()const {auto ret = StrBlobPtr(*this,data->size());return ret; }總結(jié)
以上是生活随笔為你收集整理的2.5w字长文爆肝 C++动态内存与智能指针一篇搞懂!太顶了!!!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苹果手机如何一键轻松去除“视频水印”?
- 下一篇: 《C++ Primer》10.1节练习