| shared_ptr和unique_ptr都支持的操作 | 解釋 |
|---|---|
| shared_ptr |
空智能指標,可以指向型別為T的物件 |
| p | 將p用作一個條件判斷,若p指向一個物件,則為true |
| *p | 解參考p,獲得它指向的物件 |
| p->mem | 等價于(*p).mem |
| p.get() | 回傳p中保存的指標,要小心使用,若智能指標釋放了其物件,回傳的指標所指向的物件也就消失了 |
| swap(p, q)或p.swap(q) | 交換p和q中的指標 |
| shared_ptr獨有的操作 | 解釋 |
|---|---|
| make_shared |
回傳一個shared_ptr,指向一個動態分配的型別為T的物件,使用args初始化此物件 |
| shared_ptr |
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共享物件的智能指標數量;可能很慢,主要用于除錯 |
-
shared_ptr允許多個指標指向同一個物件;unique_ptr則“獨占”所指向的物件,標準庫還定義了一個名為weak_ptr的伴隨類,它是一種弱參考,指向shared_ptr所管理的物件,這三種型別都定義在
memory頭檔案中, -
make_shared的標準庫函式在動態記憶體中分配一個物件并初始化它,回傳指向此物件的shared_ptr,與智能指標一樣,make_shared也定義在頭檔案memory中,// 指向一個值為42的int的shared_ptr shared_ptr<int> p3 = make_shared<int>(42); // p4指向一個值為“9999999999”的string shared_ptr<string> p4 = make_shared<string>(10, '9'); // p5指向一個值初始化的int,即,值為0 shared_ptr<int> p5 = make_shared<int>();我們通常用auto定義一個物件來保存make_shared的結果,這種方式較為簡單,
// p6指向一個動態分配的空vector<string> auto p6 = make_shared<vector<string>>(); -
shared_ptr自動銷毀所管理的物件,shared_ptr還會自動釋放相關聯的記憶體,如果你將shared_ptr存放于一個容器中,而后不再需要全部元素,而只使用其中一部分,要記得用erase洗掉不再需要的那些元素,
-
程式使用動態記憶體出于以下三種原因之一:
- 程式不知道自己需要使用多少物件
- 程式不知道所需物件的準確型別
- 程式需要在多個物件間共享資料
-
示例:定義StrBlob類
/* * 功能:我們希望Blob物件的不同拷貝之間共享相同的元素, * 即,當我們拷貝一個Blob時,原Blob物件及其拷貝應該參考相同的底層元素, */ #include <iostream> #include <vector> #include <string> #include <memory> using namespace std; class StrBlob { public: typedef vector<string>::size_type size_type; // 建構式 StrBlob() : data(make_shared<vector<string>>()) {} StrBlob(initializer_list<string> il) : data(make_shared<vector<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() { check(0, "pop_back on empty StrBlob"); data->pop_back(); } // 元素訪問:非const和const版本 string &front() { // 如果vector為空,check會拋出一個例外 check(0, "front on empty StrBlob"); return data->front(); } string &back() { check(0, "back on empty StrBlob"); return data->back(); } const string &front() const { check(0, "const front on empty StrBlob"); return data->front(); } const string &back() const { check(0, "const back on empty StrBlob"); return data->back(); } private: shared_ptr<vector<string>> data; // 如果data[i]不合法,拋出一個例外 void check(size_type i, const string msg) const { if (i >= data->size()) throw out_of_range(msg); } }; -
默認情況下,動態分配的物件是默認初始化的,這意味著內置型別或組合型別的物件的值將是未定義的,而型別別物件將用默認建構式進行初始化,
-
我們可以使用傳統的構造方式(使用圓括號),也可以使用串列初始化(使用花括號):
int *pi = new int(1024); // pi指向的物件的值為1024 string *ps = new string(10, '9'); // *ps為“9999999999” // vector有10個元素,值依次從0到9 -
也可以對動態分配的物件進行值初始化,只需在型別名之后跟一對空括號即可:
string *ps1 = new string; // 默認初始化為空string string *ps = new string(); // 值初始化為空string int *pi1 = new int; // 默認初始化;*pi1的值未定義 int *pi2 = new int(); // 值初始化為0;*pi2為0 -
如果我們提供了一個括號包圍的初始化器,就可以使用auto從此初始化器來推斷我們想要分配的物件的型別,但是,由于編譯器要用初始化器的型別來推斷要分配的型別,只有當括號中僅有單一初始化器時才可以使用auto:
auto p1 = new auto(obj); // p指向一個與obj型別相同的物件 // 該物件用obj進行初始化 auto p2 = new auto{a, b, c}; // 錯誤:括號中只能有單個初始化器 -
用new分配const物件是合法的:
// 分配并初始化一個const int const int *pci = new const int(1024); // 分配并默認初始化一個const的空string const string *pcs = new const string; -
類似其他任何const物件,一個動態分配的const物件必須進行初始化,對于一個定義了默認建構式的型別別,其const動態物件可以隱式初始化,而其它型別的物件就必須顯式初始化,由于分配的物件是const的,new回傳的指標是一個指向const的指標,
-
默認情況下,如果new不能分配所要求的記憶體空間,它會拋出一個型別為
bad_alloc的例外, -
定位new運算式允許我們向new傳遞額外的引數,在此例中,我們傳遞給它一個由標準庫定義的名為
nothrow的物件,如果將nothrow傳遞給new,我們的意圖是告訴它不能拋出例外,如果這種形式的new不能分配所需記憶體,它會回傳一個空指標,bad_alloc和nothrow都定義在頭檔案new中,// 如果分配失敗,new回傳一個空指標 int *p1 = new int; // 如果分配失敗,new拋出std::bad_alloc int *p2 = new (nothrow) int; // 如果分配失敗,new回傳一個空指標 -
傳遞給delete的指標必須指向動態分配的記憶體,或者是一個空指標,
-
雖然一個const物件的值不能被改變,但它本身是可以被銷毀的:
const int *pci = new const int(1024); delete pci; // 正確:釋放一個const物件 -
由內置指標(而不是智能指標)管理的動態記憶體在被顯式釋放前一直都會存在,
-
空懸指標:指向一塊曾經保存資料物件但現在已經無效的記憶體的指標,
-
避免空懸指標問題:在指標即將要離開其作用域之前釋放掉它所關聯的記憶體,如果我們需要保留指標,可以在
delete之后將nullptr賦予指標,這樣就清楚地指出指標不指向任何物件,這只是提供了有限的保護, -
可以用new回傳的指標來初始化智能指標:
shared_ptr<double> p1; // shared_ptr可以指向一個double shared_ptr<int> p2(new int(42)); // p2指向一個值為42的int -
接受指標引數的智能指標建構式是explicit的,因此,我們不能將一個內置指標隱式轉化為一個智能指標,必須使用直接初始化形式來初始化一個智能指標:
shared_ptr<int> p1 = new int(1024); // 錯誤:必須使用直接初始化形式 shared_ptr<int> p2(new int(1024)); // 正確;使用了直接初始化形式一個回傳
shared_ptr的函式不能在其回傳陳述句中隱式轉換一個普通指標:shared_ptr<int> clone(int p) { return new int(p); // 錯誤:隱式轉換為shared_ptr<int> }我們必須將
shared_ptr顯式系結到一個想要回傳的指標上:shared_ptr<int> clone(int p) { // 正確:顯式地用int*創建shared_ptr<int> return shared_ptr<int>(new int(p)); } -
默認情況下,一個用來初始化智能指標的普通指標必須指向動態記憶體,因為智能指標默認使用delete釋放它所關聯的物件,我們可以將智能指標系結到一個指向其他型別的資源的指標上,但是為了這樣做,必須提供自己的操作來替代delete,
| 定義和改變shared_ptr的其他方法 | 解釋 |
|---|---|
| shared_ptr |
p管理內置指標q所指向的物件;q必須指向new分配的記憶體,且能夠轉換為T*型別 |
| shared_ptr |
p從unique_ptr u那里接管了物件的所有權;將u置為空 |
| shared_ptr |
p接管了內置指標q所指向的物件的所有權,q必須能轉換為T*型別,p將使用可呼叫物件d來代替delete |
| shared_ptr |
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 |
-
不要混合使用普通指標和智能指標,shared_ptr可以協調物件的析構,但這僅限于其自身的拷貝(也是shared_ptr)之間,
// 在函式被呼叫時ptr被創建并初始化 void process(shared_ptr<int> ptr) { // 使用ptr } // ptr離開作用域,被銷毀 int main() { shared_ptr<int> p(new int(42)); // 參考計數為1 process(p); // 拷貝p會遞增它的參考計數;在process中參考計數值為2 int i = *p; // 正確:參考計數值為1 int *x(new int(1024)); // 危險:x是一個普通指標,不是一個智能指標 process(x); // 錯誤:不能將int*轉換為一個shared_ptr<int> process(shared_ptr<int>(x)); // 合法的,但記憶體會被釋放! int j = *x; // 未定義的:x是一個空懸指標! return 0; }使用一個內置指標來訪問一個智能指標所負責的物件是很危險的,因為我們無法知道物件何時會被銷毀,
-
get用來將指標的訪問權限傳遞給代碼,你只有在確定代碼不會delete指標的情況下,才能使用get,特別是,永遠不要用get初始化另一個智能指標或者為另一個智能指標賦值,
-
函式退出有兩種可能,正常處理結束或者發生了例外,無論哪種情況,區域物件都會被銷毀,所以,如果使用智能指標,即使程式塊過早結束,智能指標類也能確保在記憶體不再需要時將其釋放:
void f() { shared_ptr<int> sp(new int(42)); // 分配一個新物件 // 這段代碼拋出一個例外,且在f中未被捕獲 } // 在函式結束時shared_ptr自動釋放記憶體 -
如果使用內置指標管理記憶體,且在new之后在對應的delete之前發生了例外,則記憶體不會被釋放,
-
智能指標可以提供對動態分配的記憶體安全而又方便的管理,但這建立在正確使用的前提下,為了正確使用智能指標,我們必須堅持一些基本規范:
- 不使用相同的內置指標值初始化(或reset)多個智能指標,
- 不delete get()回傳的指標,
- 不使用get()初始化或reset另一個智能指標,
- 如果你使用get()回傳的指標,記住當最后一個對應的智能指標銷毀后,你的指標就變為無效了,
- 如果你使用智能指標管理的資源不是new分配的記憶體,記住傳遞給它一個洗掉器,
-
一個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<string> p1(new string("Stogosaurus")); unique_ptr<string> p2(p1); // 錯誤:unique_ptr不支持拷貝 unique_ptr<string> p3; p3 = p2; // 錯誤:unique_ptr不支持賦值
| unique_ptr操作 | 解釋 |
|---|---|
| unique_ptr |
空unique_ptr,可以指向型別為T的物件,u1會使用delete來釋放它的指標;u2會使用一個型別為D的可呼叫物件來釋放它的指標 |
| unique_ptr<T, D> u(d) | 空unique_ptr,指向型別為T的物件,用型別為D的物件d代替delete |
| u = nullptr | 釋放u指向的物件,將u置為空 |
| u.release() | u放棄對指標的控制權,回傳指針,并將u置為空 |
| u.reset()或u.reset(q)或u.reset(nullptr) | 釋放u指向的物件,如果提供了內置指標q,令u指向這個物件;否則將u置為空 |
-
雖然我們不能拷貝或賦值unique_ptr,但可以通過呼叫release或reset將指標的所有權從一個(非const)unique_ptr轉移給另一個unique:
// 將所有權從p1(指向string Stogosaurus)轉移給p2 unique_ptr<string> p2(p1.release()); // release將p1置為空 unique_ptr<string> p3(new string("Trex")); // 將所有權從p3轉移給p2 p2.reset(p3.release()); // reset釋放了p2原來指向的記憶體 -
呼叫release會切斷unique_ptr和它原來管理的物件間的聯系,release回傳的指標通常被用來初始化另一個智能指標或給另一個智能指標賦值,但是,如果我們不用另一個智能指標來保存release回傳的指標,我們的程式就要負責資源的釋放:
p2.release(); // 錯誤:p2不會釋放記憶體,而且我們丟失了指標 auto p = p2.release(); // 正確,但我們必須記得delete(p) -
不能拷貝unique_ptr的規則有一個例外:我們可以拷貝或賦值一個將要被銷毀的unique_ptr,最常見的例子是從函式回傳一個unique_ptr:
unique_ptr<int> clone(int p) { // 正確:從int*創建一個unique_ptr<int> return unique_ptr<int>(new int(p)); }還可以回傳一個區域物件的拷貝
unique_ptr<int> clone(int p) { unique_ptr<int> ret(new int(p)); // ... return ret; } -
多載一個unique_ptr中的洗掉器會影響到unique_ptr型別以及如何構造(或reset)該型別的物件,與多載關聯容器的比較操作類似,我們必須在尖括號中unique_ptr指向型別之后提供洗掉器型別,在創建或reset一個這種unique_ptr型別的物件時,必須提供一個指定型別的可呼叫物件(洗掉器)
// p指向一個型別為objT的物件,并使用一個型別為delT的物件釋放objT物件 // 它會呼叫一個名為fcn的delT型別物件 unique_ptr<objT, delT> p(new objT, fcn);void f(destination &d /* 其他需要的引數 */) { connection c = connect(&d); // 打開連接 // 當p被銷毀時,連接將會關閉 unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection); // 使用連接 // 當f退出時(即使是由于例外而退出),connection會被正確關閉 } -
weak_ptr是一種不控制所指向物件生存期的智能指標,它指向由一個shared_ptr管理的物件,將一個weak_ptr系結到一個shared_ptr不會改變shared_ptr的參考計數,
| weak_ptr | 解釋 |
|---|---|
| weak_ptr |
空weak_ptr可以指向型別為T的物件 |
| weak_ptr |
與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 |
-
由于物件可能不存在,我們不能使用weak_ptr直接訪問物件,而必須呼叫lock,
auto p = make_shared<int>(42); weak_ptr<int> wp(p); // wp弱共享p;p的參考計數未改變 if (shared_ptr<int> np = wp.lock()) // 如果np不為空則條件成立 { // 在if中,np與p共享物件 // ... }只有當lock呼叫回傳true時我們才會進入if陳述句體,在if中,使用np訪問共享物件是安全的,
-
示例:StrBlobPtr
/* * 功能:為StrBlob新增一個伴隨指標類 */ #include <iostream> #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); StrBlob(vector<string> *p); 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后才能定義這兩個函式 StrBlobPtr end(); // const版本 StrBlobPtr begin() const; StrBlobPtr end() const; private: shared_ptr<std::vector<std::string>> data; // 如果data[i]不合法,拋出一個例外 void check(size_type i, const std::string &msg) const; }; inline StrBlob::StrBlob(): data(make_shared<vector<string>>()) { } inline StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) { } inline StrBlob::StrBlob(vector<string> *p): data(p) { } 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會拋出一個例外 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(); } // 當試圖訪問一個不存在的元素時,StrBlobPtr拋出一個例外 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) { } StrBlobPtr(const StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { } string& deref() const; string& deref(int off) const; StrBlobPtr& incr(); // 前綴遞增 StrBlobPtr& decr(); // 前綴遞減 private: // 若檢查成功,check回傳一個指向vector的shared_ptr shared_ptr<vector<string>> check(size_t, const string&) const; // 保存一個weak_ptr,意味著底層vector可能會被銷毀 weak_ptr<vector<string>> wptr; size_t curr; // 在陣列中的當前位置 }; 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; // 否則,回傳指向vector的shared_ptr } inline string& StrBlobPtr::deref() const { auto p = check(curr, "dereference past end"); return (*p)[curr]; // (*p)是物件所指向的vector } inline string& StrBlobPtr::deref(int off) const { auto p = check(curr + off, "dereference past end"); return (*p)[curr + off]; // (*p)是物件所指向的vector } // 前綴遞增:回傳遞增后的物件的參考 inline StrBlobPtr& StrBlobPtr::incr() { // 如果curr已經指向容器的尾后位置,就不能遞增它 check(curr, "increment past end of StrBlobPtr"); ++curr; // 推進當前位置 return *this; } // 前綴遞減:回傳遞減后的物件的參考 inline StrBlobPtr& StrBlobPtr::decr() { // 如果curr已經為0,遞減它就會產生一個非法下標 --curr; // 遞減當前位置 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; } // const版本 inline StrBlobPtr StrBlob::begin() const { return StrBlobPtr(*this); } inline StrBlobPtr StrBlob::end() const { 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是同一個 if (l == r) { // 則兩個指標都是空,或者指向相同元素時,它們相等 return (!r || lhs.curr == rhs.curr); } else { return false; // 若指向不同vector,則不可能相等 } } inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs) { return !eq(lhs, rhs); } -
為了讓new分配一個物件陣列,我們要在型別名之后跟一對方括號,在其中指明要分配的物件的數目,在下例中,new分配要求數量的物件并(假定分配成功后)回傳指向第一個物件的指標:
// 呼叫get_size確定分配多少個int int *pia = new int[get_size()]; // pia指向第一個int方括號中的大小必須是整型,但不必是常量,
-
分配一個陣列會得到一個元素型別的指標,而非一個陣列型別的物件,由于分配的記憶體并不是一個陣列型別,因此不能對動態陣列呼叫begin或end,也不能用范圍for陳述句來處理(所謂的)動態陣列中的元素,要記住我們所說的動態陣列并不是陣列型別,這是很重要的,
-
默認情況下,new分配的物件,不管是單個分配的還是陣列中的,都是默認初始化的,可以對陣列中的元素進行值初始化,方法是在大小之后跟一對空括號,我們還可以提供一個元素初始化器的花括號串列,
int *pia = new int[10]; // 10個未初始化的int int *pia2 = new int[10](); // 10個值初始化為0的int string *psa = new string[10]; // 10個空string string *psa2 = new string[10](); // 10個空string // 10個int分別用串列中對應的初始化器初始化 int *pia3 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // 10個string,前4個用給定的初始化器初始化,剩余的進行值初始化 string *ps3 = new string[10]{"a", "an", "the", string(3,'x')}; -
如果初始化器數目小于元素數目,剩余元素將進行值初始化,如果初始化器數目大于元素數目,則new運算式失敗,不會分配任何記憶體,new會拋出一個型別為
bad_array_new_length的例外,類似bad_alloc,此型別定義在頭檔案new中, -
雖然我們用空括號對陣列中元素進行值初始化,但不能在括號中給出初始化器,這意味著不能用auto分配陣列,
-
動態分配一個空陣列是合法的,當我們用new分配一個大小為0的陣列時,new回傳一個合法的非空指標,此指標保證與new回傳的其他任何指標都不相同,我們可以像使用尾后迭代器一樣使用這個指標,但此指標不能解參考——畢竟它不指向任何元素,
char arr[0]; // 錯誤:不能定義長度為0的陣列 char *cp = new char[0]; // 正確:但cp不能解參考 -
為了釋放動態陣列,我們使用一種特殊形式的delete——在指標前加上一個空方括號對:
delete p; // p必須指向一個動態分配的物件或為空 delete [] pa; // pa必須指向一個動態分配的陣列或為空 -
陣列中的元素按逆序銷毀,即,最后一個元素首先被銷毀,然后是倒數第二個,以此類推,
-
當我們使用一個型別別名來定義一個陣列型別時,在new運算式中不使用[],即使是這樣,在釋放一個陣列指標時也必須使用方括號,
typedef int arrT[42]; // arrT是42個int的陣列的型別別名 int *p = new arrT; // 分配一個42個int的陣列;p指向第一個元素 delete [] p; // 方括號是必需的,因為我們當初分配的是一個陣列 -
如果我們在delete一個陣列指標時忘記了方括號,或者在delete一個單一物件的指標時使用了方括號,編譯器很可能不會給出警告,我們的程式可能在執行程序中在沒有任何警告的情況下行為例外,
-
標準庫提供了一個可以管理new分配的陣列的unique_ptr版本,為了用一個unique_ptr管理動態陣列,我們必須在物件型別后面跟一對空方括號:
// up指向一個包含10個未初始化int的陣列 unique_ptr<int[]> up(new int[10]); up.release(); // 自動用delete[]銷毀其指標型別說明符中的方括號(<int[]>)指出up指向一個int陣列而不是一個int,由于up指向一個陣列,當up銷毀它管理的指標時,會自動使用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必須指向一個陣列 |
-
與
unique_ptr不同,shared_ptr不直接支持管理動態陣列,如果希望使用shared_ptr管理一個動態陣列,必須提供自己定義的洗掉器:// 為了使用shared_ptr,必須提供一個洗掉器 shared_ptr<int> sp(new int[10], [](int *p){ delete[] p; }); sp.reset(); // 使用我們提供的lambda釋放陣列,它使用delete[]如果未提供洗掉器,這段代碼將是未定義的,默認情況下,
shared_ptr使用delete銷毀它指向的物件, -
shared_ptr未定義下標運算子,而且智能指標型別不支持指標算術運算,因此,為了訪問陣列中的元素,必須用get獲取一個內置指標,然后用它來訪問陣列元素,// shared_ptr未定義下標運算子,并且不支持指標的算術運算 for (size_t i = 0; i != 10; ++i) *(sp.get() + i) = i; // 使用get獲取一個內置指標 -
new有一些靈活性上的局限,其中一方面表現在它將記憶體分配和物件構造組合在了一起,類似的,delete將物件析構和記憶體釋放組合在了一起,更重要的是,那些沒有默認建構式的類就不能動態分配陣列了, -
標準庫allocator類定義在頭檔案
memory中,它幫助我們將記憶體分配和物件構造分離開來,
| 標準庫allocator類及其演算法 | 解釋 |
|---|---|
| allocator |
定義了一個名為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*的指標,指向一塊原始記憶體;args被傳遞給型別為T的建構式,用來在p指向的記憶體中構造一個物件 |
| a.destroy(p) | p為T*型別的指標,此演算法對p指向的物件執行解構式 |
allocator<string> alloc; // 可以分配string的allocator物件
auto const p = alloc.allocate(n); // 分配n個未初始化的string
auto q = p; // q指向最后構造的元素之后的位置
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
alloc.deallocate(p, n); // 釋放記憶體
- 為了使用
allocate回傳的記憶體,我們必須用construct構造物件,使用未構造的記憶體,其行為是未定義的, - 我們只能對真正構造了的元素進行
destroy操作,
| allocator演算法 | 解釋 |
|---|---|
| —— | 這些函式在給定目的位置創建元素,而不是由系統分配記憶體給它們 |
| uninitialized_copy(b, e, b2) | 從迭代器b和e指出的輸入范圍中拷貝元素到迭代器b2指定的未構造的原始記憶體中,b2指向的記憶體必須足夠大,能容納輸入序列中元素的拷貝 |
| uninitialized_copy_n(b, n, b2) | 從迭代器b指向的元素開始,拷貝n個元素到b2開始的記憶體中 |
| uninitialized_fill(b, e, t) | 在迭代器b和e指定的原始記憶體范圍中創建物件,物件的值均為t的拷貝 |
| uninitialized_fill_n(b, n, t) | 從迭代器b指向的記憶體地址開始創建n個物件,b必須指向足夠大的未構造的原始記憶體,能夠容納給定數量的物件 |
-
類似
copy,uninitialized_copy回傳(遞增后的)目的位置迭代器,因此,一次uninitialized_copy呼叫會回傳一個指標,指向最后一個構造的元素之后的位置,// 分配比vi中元素所占用空間大一倍的動態記憶體 auto p = alloc.allocate(vi.size() * 2); // 通過拷貝vi中的元素來構造從p開始的元素 auto q = uninitialized_copy(vi.begin(), vi.end(), p); // 將剩余元素初始化為42 uninitialized_fill_n(q, vi.size(), 42); -
如果兩個類概念上“共享”了資料,可以使用
shared_ptr來反映資料結構中的這種共享關系, -
當我們設計一個類時,在真正實作成員之前先撰寫程式使用這個類,是一種非常有用的方法,通過這種方法,可以看到類是否具有我們所需要的操作,
-
使用標準庫:文本查詢程式
-
my_TextQuery.cpp
#include "my_TextQuery.h" #include "make_plural.h" #include <cstddef> #include <memory> #include <sstream> #include <string> #include <vector> #include <map> #include <set> #include <iostream> #include <fstream> #include <cctype> #include <cstring> #include <utility> using std::size_t; using std::shared_ptr; using std::istringstream; using std::string; using std::getline; using std::vector; using std::map; using std::set; using std::cerr; using std::cout; using std::cin; using std::ostream; using std::endl; using std::ifstream; using std::ispunct; using std::tolower; using std::strlen; using std::pair; // read the input file and build the map of lines to line numbers TextQuery::TextQuery(ifstream &is): file(new vector<string>) { string text; while (getline(is, text)) { // for each line in the file file.push_back(text); // remember this line of text int n = file.size() - 1; // the current line number istringstream line(text); // separate the line into words string word; while (line >> word) { // for each word in that line word = cleanup_str(word); // if word isn't already in wm, subscripting adds a new entry auto &lines = wm[word]; // lines is a shared_ptr if (!lines) // that pointer is null the first time we see word lines.reset(new set<line_no>); // allocate a new set lines->insert(n); // insert this line number } } } // not covered in the book -- cleanup_str removes // punctuation and converts all text to lowercase so that // the queries operate in a case insensitive manner string TextQuery::cleanup_str(const string &word) { string ret; for (auto it = word.begin(); it != word.end(); ++it) { if (!ispunct(*it)) ret += tolower(*it); } return ret; } QueryResult TextQuery::query(const string &sought) const { // we'll return a pointer to this set if we don't find sought static shared_ptr<set<line_no>> nodata(new set<line_no>); // use find and not a subscript to avoid adding words to wm! auto loc = wm.find(cleanup_str(sought)); if (loc == wm.end()) return QueryResult(sought, nodata, file); // not found else return QueryResult(sought, loc->second, file); } ostream &print(ostream & os, const QueryResult &qr) { // if the word was found, print the count and all occurrences os << qr.sought << " occurs " << qr.lines->size() << " " << make_plural(qr.lines->size(), "time", "s") << endl; // print each line in which the word appeared for (auto num : *qr.lines) // for every element in the set // don't confound the user with text lines starting at 0 os << "\t(line " << num + 1 << ") " << qr.file.begin().deref(num) << endl; return os; } // debugging routine, not covered in the book void TextQuery::display_map() { auto iter = wm.cbegin(), iter_end = wm.cend(); // for each word in the map for ( ; iter != iter_end; ++iter) { cout << "word: " << iter->first << " {"; // fetch location vector as a const reference to avoid copying it auto text_locs = iter->second; auto loc_iter = text_locs->cbegin(), loc_iter_end = text_locs->cend(); // print all line numbers for this word while (loc_iter != loc_iter_end) { cout << *loc_iter; if (++loc_iter != loc_iter_end) cout << ", "; } cout << "}\n"; // end list of output this word } cout << endl; // finished printing entire map }-
my_TextQuery.h
#ifndef TEXTQUERY_H #define TEXTQUERY_H #include <memory> #include <string> #include <vector> #include <map> #include <set> #include <fstream> #include "my_QueryResult.h" /* this version of the query classes includes two * members not covered in the book: * cleanup_str: which removes punctuation and * converst all text to lowercase * display_map: a debugging routine that will print the contents * of the lookup mape */ class QueryResult; // declaration needed for return type in the query function class TextQuery { public: using line_no = std::vector<std::string>::size_type; TextQuery(std::ifstream&); QueryResult query(const std::string&) const; void display_map(); // debugging aid: print the map private: StrBlob file; // input file // maps each word to the set of the lines in which that word appears std::map<std::string, std::shared_ptr<std::set<line_no>>> wm; // canonicalizes text: removes punctuation and makes everything lower case static std::string cleanup_str(const std::string&); }; #endif-
make_plural.h
#include <cstddef> using std::size_t; #include <string> using std::string; #include <iostream> using std::cout; using std::endl; #ifndef MAKE_PLURAL_H #define MAKE_PLURAL_H // return the plural version of word if ctr is greater than 1 inline string make_plural(size_t ctr, const string &word, const string &ending) { return (ctr > 1) ? word + ending : word; } #endif-
my_QueryResult.h
#ifndef QUERYRESULT_H #define QUERYRESULT_H #include <memory> #include <string> #include <vector> #include <set> #include <iostream> #include "my_StrBlob.h" class QueryResult { friend std::ostream& print(std::ostream&, const QueryResult&); public: typedef std::vector<std::string>::size_type line_no; typedef std::set<line_no>::const_iterator line_it; QueryResult(std::string s, std::shared_ptr<std::set<line_no>> p, StrBlob f): sought(s), lines(p), file(f) { } std::set<line_no>::size_type size() const { return lines->size(); } line_it begin() const { return lines->cbegin(); } line_it end() const { return lines->cend(); } StrBlob get_file() { return file; } private: std::string sought; // word this query represents std::shared_ptr<std::set<line_no>> lines; // lines it's on StrBlob file; //input file }; std::ostream &print(std::ostream&, const QueryResult&); #endif-
my_StrBlob.h
#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); StrBlob(vector<string> *p); 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后才能定義這兩個函式 StrBlobPtr end(); // const版本 StrBlobPtr begin() const; StrBlobPtr end() const; private: shared_ptr<std::vector<std::string>> data; // 如果data[i]不合法,拋出一個例外 void check(size_type i, const std::string &msg) const; }; inline StrBlob::StrBlob(): data(make_shared<vector<string>>()) { } inline StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) { } inline StrBlob::StrBlob(vector<string> *p): data(p) { } 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會拋出一個例外 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(); } // 當試圖訪問一個不存在的元素時,StrBlobPtr拋出一個例外 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) { } StrBlobPtr(const StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { } string& deref() const; string& deref(int off) const; StrBlobPtr& incr(); // 前綴遞增 StrBlobPtr& decr(); // 前綴遞減 private: // 若檢查成功,check回傳一個指向vector的shared_ptr shared_ptr<vector<string>> check(size_t, const string&) const; // 保存一個weak_ptr,意味著底層vector可能會被銷毀 weak_ptr<vector<string>> wptr; size_t curr; // 在陣列中的當前位置 }; 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; // 否則,回傳指向vector的shared_ptr } inline string& StrBlobPtr::deref() const { auto p = check(curr, "dereference past end"); return (*p)[curr]; // (*p)是物件所指向的vector } inline string& StrBlobPtr::deref(int off) const { auto p = check(curr + off, "dereference past end"); return (*p)[curr + off]; // (*p)是物件所指向的vector } // 前綴遞增:回傳遞增后的物件的參考 inline StrBlobPtr& StrBlobPtr::incr() { // 如果curr已經指向容器的尾后位置,就不能遞增它 check(curr, "increment past end of StrBlobPtr"); ++curr; // 推進當前位置 return *this; } // 前綴遞減:回傳遞減后的物件的參考 inline StrBlobPtr& StrBlobPtr::decr() { // 如果curr已經為0,遞減它就會產生一個非法下標 --curr; // 遞減當前位置 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; } // const版本 inline StrBlobPtr StrBlob::begin() const { return StrBlobPtr(*this); } inline StrBlobPtr StrBlob::end() const { 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是同一個 if (l == r) { // 則兩個指標都是空,或者指向相同元素時,它們相等 return (!r || lhs.curr == rhs.curr); } else { return false; // 若指向不同vector,則不可能相等 } } inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs) { return !eq(lhs, rhs); } #endif -
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/258309.html
標籤:C++
下一篇:OpenCL學習(1)
