C++自修入門實境秀、C++ Primer 5版研讀秀 83/ ~v12.1. Dynamic Memory and Smart Pointer...



頁462

改中文版作文:第83集起11:10

在這個例子中,p是指向factory在記憶體區配置資源的唯一指標。一旦use_factory函式結束,在整個應用程式執行期間就沒有任何辦法再去釋放那些配置所佔用的記憶體資源了。因為這整個應用程式的執行流程(logic)不會再有機會來釋放這個factory函式配置所耗用的記憶體資源,所以我們必須要在執行use_factory函式的期間,釋放那個記憶體資源,以此來剔除這個臭蟲: 25:20

void use_factory(T arg)

{

Foo *p = factory(arg);

//使用p

delete p; //既然後續的應用程式操作都不會再用到p所指向的物件,我們就該在離開use_factory前,釋放該物件所佔用的記憶體資源

}

或者,如果這個應用程式系統的其他地方(程式碼)還有需要用到use_factory配置出來的物件的,那麼我們就應該將use_factory函式改寫成能回傳一個指標指向它所配置的記憶體位址:

Foo* use_factory(T arg)

{

Foo *p = factory(arg);

//使用p

return p; //調用use_factory的指令必須負責清除use_factory所佔用的記憶體

}

注意:控管動態記憶體是非常容易出錯的

使用new和delete 來控管動態記憶體常須面臨三種難題:

1. 忘記清除記憶體(忘了對佔用的記憶體執行delete運算)。忘了將無用的動態記憶體資源清理乾淨,這樣的編程缺失,被稱作是「記憶體耗損(洩漏、紕漏,memory leak)」或者說「記憶體浪費、記憶體耗漏」;因為那些無用的記憶體資源在整個應用程式執行期間不再有機會回歸到記憶體的自由存放區(free store)了。若想要進行程式碼的檢測以找出這樣的記憶體耗漏是很困難的,因為記憶體耗漏通常是無法偵測出來的,除非應用程式執行得夠久,久到應用程式的這個耗漏不斷地重複、而耗盡了所有的記憶體資源,這個時候才有機會發現這個紕漏出在哪裡。

2. 在物件被刪除後還企圖使用它。這種錯誤有的時候可以藉由刪除該物件之後隨即將指向它的指標設為null來偵測。(對一個指標下達delete運算後,即將該指標指定(assign,=)為nullptr。下文「懸置指標」的部分就是在談這個課題!)1:10:50

3. 在相同的記憶體位址上進行重複地刪除。當有兩個以上指標指向(定址)同一個動態配置的物件時,這種錯誤就可能發生。只要對其中一個指標進行delete運算,那麼該指標所指的物件其記憶體資源就會歸還於自由存放區內。如果我們又將第二個指標delete ,那麼自由存放區(free store)就有可能毀損(may be corrupted)。

以上錯誤是很容易犯的,但要發現這些錯誤或修正它們卻是非常地困難。

養成好習慣:可以利用智慧指標來全權控管動態記憶體以避免以上諸多問題。智慧指標只會在沒有其他的智慧指標指向該記憶體位址時,它才會刪除它所指向的物件,並釋放物件所佔用的記憶體資源。

在對一個指標執行delete運算後,最好重置其值… 1:16:58

當對一個指標進行了delete運算後,那個指標所指的物件就被刪除了,該指標也就跟著無效(invalid)。雖然那個指標已經無效——即不再指向存在的物件,但在許多電腦系統裡,該指標仍會持續存在、並依然指向那個早被釋放的動態記憶體位址。在這種情形下,該指標就會變成所謂的懸置指標(dangling pointer)。只要一個指標曾經指向某個物件佔用的記憶體位址,但在該物件被清除之後,這個指標卻依然指向它佔用的記憶體位址,這種已然無效的指標(invalid),就叫作「懸置指標」。(類似「抱柱信」或者「刻舟求劍」,故亦可呼之為「呆滯指標」)

頁463

改中文版作文:1:35:40 1:41:13

未初始化的指標會有的問題(§2.3.2,頁54),懸置指標也都有。那麼,要怎麼才能有效地避免一個指標成為懸置指標呢?我們可以待到指標就要離開它的生命範疇之前(just before),才對它執行delete運算,來清除與它關聯的記憶體,不要太早對它執行delete運算,這樣就可以避免該指標淪為懸置指標了。在這樣的時機下對指標進行delete運算,才不會讓該指標在經過delete運算後,還有機會被其他的程式碼調用,而成為所謂的懸置指標。如果我們非得、或想要保留那個指標,我們也可以在對它使用了 delete後,將它指定為nullptr,這樣就能讓我們的程式系統明白那個指標已不再指向任何存在的物件了,它就不會淪為懸置指標。

…即使這樣做,也只能提供有限的防護 1:58:30

動態記憶體的一個根本問題就在於:同時可能會有好幾個指標指向相同的記憶體位址。因此,即使我們重置了一個經過delete 後的指標值,但這樣的防範,依然可能只是顧此失彼、掛一漏萬的;2:10:00因為我們這樣的操作,只能保證那個經過delete運算的指標,不會淪為懸置指標,但對於那些仍然指向那個(已清除的)記憶體位址的其他指標而言,卻沒有任何連帶的影響。比如:

int *p(new int(42));//p這個指標指向了動態記憶體,是由直接初始化(direct initialize)的方式來設定其值

auto q = p; // p和q都指向了相同的記憶體位址

delete p; //對p做delete,就使得p和q都無效了

p = nullptr; //將p之值指定(重置)為null,即指明p這個指標不再指向一個存在的物件。但q呢?並沒有任何影響,依然還是指向那個記憶體位址,淪為了懸置指標(dangling pointer)

在此,p和q兩個指標都指向了同一個動態配置的物件。我們對p執行了delete運算,清除了佔用的記憶體,隨即將p設為 nullptr,指明了p指標不再指向一個存在的物件。然而,對p這樣的重置作業,對q並沒有任何影響;q早在我們對p進行delete運算時,就成為一個無效的指標了,因為它所指向的記憶體位址和p是完全相同的!在實際的實務上,要找出指向相同的全部指標,幾乎是不可能的事(那種困難度,不是我們所能想像的surprisingly difficult)。

習題章節12.1.2 2:51:20

練習12.6 :

寫一個函式回傳一個由int所構成、且是動態配置的vector。將那個vector作為引數傳入另一個函式(也就是這個函式的參數列中必有一個型別為vector<int>的參數),這個函式會以讀取標準輸入(standard input)的方式來設定那個vector裡的元素值。將那個vector當作引數再傳入另一個函式,並用這個函式印出這個vector的元素值。記得在適當的時機對那個vector進行delete運算,以清除不再需要用到的記憶體資源。

練習12.7 : 3:06:58 3:24:13 3:28:29

重做前面的練習,這次使用shared_ptr。

練習12.8 : 3:52:38

下列函式若有錯誤,請予解釋。

bool b() {

int* p = new int;

// ...

return p;

}

#include<iostream>

using namespace std;

//int* p;

//bool b() {

// p = new int;

// //int* p = new int;

// // ...

// return p;//沒有delete,則成記憶體耗漏、記憶體洩漏、浪費記憶體了;因為出此範疇,則new所配置的

// //動態記憶體就無法被清除了。p是不會變成懸置指標(dangling pointer),因為出此b函式範疇就自動銷毀(自動物件、區域變數)。

// //return是用傳值的方式(即拷貝),回傳的型別也與p型別不合,但有隱含轉型的關係存在

// //然而沒有delete,尤其在此例,既然要回傳指標p,就不能delete p;應該是將local p的回傳給另一個指標,以對p原所指向的動態配置的物件,可以有後續操作,包括將其delete

// //否則,就是將p已成公用物件,而非區域物件

//}

//以上是以公用物件來改寫p

//以下則是另用一個指標來接管p原指向的動態配置的物件(dynamically allocated object)

int* b() {

int* p = new int;

//int* p = new int;

// ...

return p;//沒有delete,則成記憶體耗漏、記憶體洩漏、浪費記憶體了;因為出此範疇,則new所配置的

}

int main() {

int* q = b();

if (q == 0) cout << "p 沒有指向任何物件" << endl;

else {

cout << q << endl;

cout << *q << endl;

}

//if (b() == 0) cout << "p 沒有指向任何物件" << endl;

//else cout << b() << endl;

////delete p;

delete q;

}



練習12.9 : 4:44:00 4:59:00

解釋執行下列程式碼會發生什麼事:

int *q = new int(42), *r = new int(100);

r = q;

auto q2 = make_shared<int>(42) , r2 = make_shared<int>(100);

r2 = q2; 5:12:00

int main() {

int* q = new int(42), * r = new int(100);

r = q;//因為r現在不再指向new出來的int(100),這個int(100)沒有delete掉,就成了記憶體耗漏、記憶體外洩

//int(42)現在則有二個指標指向它,這些指標就有淪為懸置指標(dangling pointer)的危險

auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);

r2 = q2;//r2原來指向的動態配置的「<int>(100)」因為沒有指向它的智慧指標,就隨之銷毀了

//「<int>(42)」這個動態配置物件則會有兩個智慧指標指向它

//q2.use_count=2,r2.use_count=0---印出來卻是2!因為r2現在等於q2了。r2.use_count=2是表示除了r2以外,還有q2也指向同一個動態配置物件

//因此,所謂的參考計數(reference count),應當時屬於物件的屬性,而不是智慧指標的!

//一個動態配置的物件有多少參考計數,就表示它有多少智慧指標指著它。英文版好像也弄錯了

cout << q2.use_count()<< endl;

cout <<r2.use_count() << endl;

}



頁464

改中文版作文:5:19:27

12.1.3 將 shared_ptr 與 new搭配使用(Using shared_ptrs with new)

就如我們所見,若沒有將一個智慧指標初始化,那麼它就會被初始化為一個null指標。如表12.3 中所述,我們也能以new回傳的一個指標來初始化一個智慧指標:

shared_ptr<double> p1; // 能夠指向一個 double 的 shared_ptr

shared_ptr<int> p2(new int(42)); //p2 指向一個int,其值為 42


智慧指標有一種建構器是接受new回傳的普通指標作為引數的,這種建構器是explicit的(§7.5.4,頁296)。就是因為這個緣故,我們就無法隱含地將一個內建的普通指標轉為智慧指標;而且我們必須使用直接形式的初始化(直接初始化(direct initialize)§3.2.1,頁84)來初始化一個智慧指標:

shared_ptr<int> p1 = new int(1024); // 錯誤:必須使用直接初始化

shared_ptr<int> p2 (new int(1024)); // ok :使用直接相始化

p1的初始化企圖將從new回傳的int*普通指標(plain pointer、ordinary pointer)直接(implicitly)建構出一個shared_ptr智慧指標(smart pointer)。因為智慧指標接受普通指標作為引數的建構器是explicit的,並不接受直接(implicitly)將一個普通指標轉為一個智慧指標,所以這個初始化是錯誤的。5:47:00也因為這樣,函式在回傳一個智慧指標shared_ptr的時候,亦不能在它的回傳述句(return statement)中使用隱含(implicitly)轉型來企圖將一個普通指標轉換為擬回傳的智慧指標shared_ptr:

shared_ptr<int> clone(int p) {

return new int(p) ;// 錯誤:隱含轉換為 shared_ptr<int>

}

我們必須明確地(explicity)將一個shared_ptr繫結(bind)至我們想要回傳的指標:

shared_ptr<int> clone (int p) {

// ok:明確地創建一個 shared_ptr<int> from int*

return shared_ptr<int>(new int(p));

}

因為在沒有指定如何清除動態配置物件(dynamically allocated object)的情況下,智慧指標是會用delete來清除該物件、並釋放其關聯的記憶體,所以預設情況下,我們必須使用指向動態記憶體的指標來初始化一個智慧指標。若我們想要將智慧指標繫結到指向非動態記憶體資源(other kinds of resources)的指標,那麼,就必須提供自訂的運算來取代delete。我們會在§ 12.1.4(頁468)看到如何寫出自己的清理資源程式碼(deletion code)。

不要將普通指標和智慧指標混著用…

shared_ptr智慧指標也只有在其他shared_ptr指標是自己拷貝出來的(are copies of itself),才能妥善地對其所指之動態配置的物件完成解構的動作(ccoordinate destruction)。確實,這也就是我們一直以來都建議各位刻意(no way to inadvertently)去使用make_shared而不要用new來控管動態記憶體的原因所在。這樣我們就能夠在使用動態記憶體來配置所需物件的同時,也將一個 shared_ptr繫結到該物件上了。因為在C++中並沒有辦法去將獨立創建——created 也就是自行宣告、定義——的各個 shared_ptr(more than one independently created shared_ptr)與一個相同的記憶體位址繫結在一塊。若我們無法在創建智慧指標的同時,將其與其所指物件相繫結,那麼我們就不再有機會這麼做了。指標不能與其所指之物繫結,那麼要那指標又有何用呢?該指標又要如何來控管該物件呢?

想像一下下列函式對智慧指標shared_ptr的操作(operates):

process函式的定義

// ptr會在process被呼叫時創建並初始化

void process(shared_ptr<int> ptr)

{

//使用ptr

}// ptr超出範疇,並被摧毁了



頁465

改中文版作文:6:57:55

因為函式的參數是以值來傳遞的(傳值pass by value),所以給process這個函式的引數會被拷貝成ptr。7:08:05拷貝一個shared_ ptr會遞增與它相關的參考計數,也就是有多少shared_ptr和它指向同一個的動態配置物件。參考計數(reference count)正確來講應該是對某個動態物件的參考計數,即有多少智慧指標指向(參考到refer to)該物件。所以應該翻成指向係數還比較傳神達意!因此,在process這個函式的區塊中,這個參考計數的值至少會是「2」。當process執行完後, ptr會被消毀,而其關聯的參考計數則隨之遞減,但卻不會降到「0」。因此,當這個區域變數ptr被摧毀時,ptr所指(refer to)的記憶體將不能得到釋放。7:18:08

因此,要正確地使用這個process函式,而不是讓它用過的記憶體得不到釋放,就應該要以shared_ptr作為引數來導入給它:

shared_ptr<int> p(new int (42)) ; // 此時p關聯的參考計數是 1

process(p) ;//拷貝p會遞增其計數;在process中,參考計數是2

int i = *p; //ok: 參考計數是1

另外值得注意的是:雖然在這個例子中,我們不能直接用一個內建型別的普通指標當作引數傳給process函式,但是卻可以用一個以內建型別new出來的普通指標來明確建構(explicitly construct)一個暫時性的shared_ptr當作引數傳給process。然而,若真的這麼做,雖然在程式的編譯與執行上或許並不會出現什麼大的錯誤,然而這麼一來,對動態記憶體的控管就得冒上很大的風險;可以想像像下面這樣的情形:

int *x(new int(1024)); //危險:x只是一個普通的指標,而非一個智慧指標

process (x) ; // 錯誤:無法將 int* 直接轉換為 shared_ptr<int>

process(shared_ptr<int>(x));// 雖然可以正確編譯,(此時等於有2個指標(一個是x這個普通指標,一個是由x創建出來的智慧指標shared_ptr)同時指向new出來的動態物件;但關聯的參考計數卻只有「1」!)但在實際執行時,一旦離開了process範疇,這個shared_ptr所指向的物件,將會隨著這個shared_ptr的消逝而被刪除!因為這時shared_ptr其關聯的參考計數在process區塊中,只有「1」!離開了區塊,就降為0,它所指的物件,也就隨之刪除。

int j = *x; //對一個已經失效的指標作解參考(dereference)運算是未定義的行為(即沒有意義的操作):此時x淪為一個懸置指標!

在上面呼叫process的程式碼中,我們將一個暫時性的shared_ptr當作引數傳給process。這個暫時性的shared_ptr會在它所在的函式process執行完畢後被摧毀。當它被摧毀時就會將它相關聯的參考計數減1,這麼一來,原本只有「1」的參考計數就會歸「0」。這時,這個暫存的shared_ptr指向的動態物件就會被刪除。

但x還是一個活指標(活著的指標),並沒有因shared_ptr的消逝而死掉,它仍然指向先前它所指向的那個記憶體位址,也就是已經被shared_ptr「掏空」的那個記憶體位址,所以x不能避免地就淪為一個懸置指標了。現在開始,對x的一切操作,都將會是未定義的、沒有意義的事了。

一旦我們將shared_ptr和普通指標繫結在一塊,我們就是將控管該記憶體的責任交給了那個shared_ptr。當我們把一個普通指標原有的責任(控管動態記憶體的責任)移交給了 shared_ptr,我們就不應該再用那個普通指標(或一個內建指標)來存取現在那個shared_ptr指向的記憶體位址。



頁466

改中文版作文:8:16:20

警告:

使用內建指標來存取智慧指標所指的物件,是很危險的,因為我們並不知道那個物件被智慧指標(在它們消失殆盡時,調用delete等效的程序來)摧毀了沒。 9:52:11 9:54:30

…而且別使用智慧指標提供的成員函式get來初始化或指定另一個智慧指標 9:59:00

智慧指標類別定義了一個名為get的成員函式(已見前表12.1),它會回傳一個內建型別的普通指標指向它所在的智慧指標所控管的物件。之所以會設計這個成員函式,主要是用在我們需要傳入一個引數給無法使用智慧指標的程式碼來使用。使用到get回傳值的程式碼,決定不能在回傳的這個指標上執行delete的運算。

即使編譯器不會指出錯誤,但若將另一個智慧指標與某個智慧指標用get所回傳的普通指標繫結起來,就是個錯誤:

shared_ptr<int> p(new int(42)) ; // 此時p所指的動態物件其參考計數是 1

int *q = p.get() ; //將q作為儲存p.get()回傳值的變數是ok的,但決定不要在可能刪除q所指物件的情境下用到這個q(don't use q in any way that might delete its pointer)

{ //新的區塊

//這是未定義的:因為兩個各自定義的shared_ptr卻指向相同的記憶體位址

shared_ptr<int>(q);

}// 當區塊結東,「 shared_ptr<int>(q)」(用q來初始化創建的shared_ptr<int>會被摧毁,當它摧毀時,因為它是獨立於p的,所以參考計數是1,摧毀後成了0,所以,會調用delete來刪除它所指向的物件,這個物件也是q所指向的,所以q指向的記憶體位址就會被釋放

// block ends, q is destroyed, and the memory to which q points is freed

int foo = *p; //這是未定義的;因為p所指的記憶體已被區塊中的shared_ptr<int>釋放了

//此例當即為 p與q與shared_ptr<int>三者共享了int(42)這個記憶體資源 10:42:00

測試的程式碼詳見: https://github.com/oscarsun72/prog1-C-Primer-5th-Edition-s-Exercises/tree/p466_Dont_Use_get_with_Another_Smart_Pointer_test

果然在這裡英文版又錯了,但是pdf錯,google 圖書版已更正了,中文版卻是根據pdf錯的來翻譯,更正版如下: 10:47:40

shared_ptr<int> p(new int(42)); // reference count is 1

int *q = p.get(); // ok: but don't use q in any way that might delete its pointer

{ // new block

// undefined: two independent shared_ptrs point to the same memory

auto local = shared_ptr<int>(q);

} // block ends, local is destroyed, and the memory to which p and q points is freed

int foo = *p; // undefined; the memory to which p points was freed

Here, p ,q,and local all point to the same memory. 在這裡,p、q和local共享了記憶體資源,都指向同一個記憶體位址。Because p and local were created independently from one other, each has a reference count of 1.因為p和local是分別宣告而定義的shared_ptr,所以它們各自有其獨立的參考計數(reference count),都是「1」。 When the inner block ends,local is destroyed. 一旦區塊執行結束,local就會被摧毀。Because local's reference count is 1,the memory to which it points will be freed. 而因為local的參考計數是1,它被摧毀後,這個參考計數就是歸0,其所指向的記憶體資源也會隨之釋出。That makes p and q into a dangling pointers;這就造成p和q都淪為了懸置指標(dangling pointer)。可見,懸置指標與是不是智慧指標、普通指標無關,只要是指標,指向了一個不復存在的物件或被清空的記憶體位址,那麼,它就是懸置指標。 what happens when we attempt to use p or q is undefined. 當p與q 成了懸置指標,對它們做任何的操作或運算都會是未定義的了。Moreover, when p is destroyed, the pointer to that memory will be deleted a second time.甚至,當智慧指標p將消逝時,與它相關的參考計數也會歸0,當它企圖去刪除它所指的物件時,此時,這個物件已不復存在,就會造成對該處記憶體位址進行第2次清除的錯誤動作,這就有可能如前所述,有損壞集區(pool)或堆置(堆積)記憶體區的危險。

在這種情況中,p和q都會指向相同的記憶體。因為它們是各自獨立創建的,兩者皆有參考計數1。當定義q的區塊結束時,q就會被摧毀。11:13:20q哪在區塊之內呢?由此可見中文版擺明就是矇著眼在那翻!!摧毀q會釋放q所指的記憶體。這使得P變 成了懸置指標,代表試著使用p時會發生什麼事,是沒有定義的。此外,當p被摧毀,對那個記憶體的指標會第二次被delete。

警告

只將get回傳的普通指標用在那些不會對這個指標進行delete運算的程式碼。特別是,絕對不要使用get回傳的指標來對另一個智慧指標進行初始化動作,或將這個指標指定給另一個智慧指標。

shared_ptr的其他運算 11:26:40

shared_ptr類別還提供了幾個其他的運算,都已列於表12.2和表12.3。我們可以使用reset成員函式(即「重設、重新設定」,重設它所指的對象object)來將一個普通指標指定給shared_ptr,使這個shared_ptr指向新的動態物件:

p = new int(1024) ; //錯誤:無法將一個普通指標指定給一個shared_ptr

p.reset(new int (1024)); // ok :透過shared_ptr類別的reset成員函式,可以將智慧指標 p 改指向一個普通指標所指的物件

就跟在對shared_ptr做指定(assignment)運算一樣,reset也會更新shared_ptr關聯的參考計數,且在此參考計數歸0時,也會刪除shared_ptr(這裡是「p」)所指的物件。reset成員函式時常會與unique成員函式一起使用,以便於妥善控管變更shared_ptr所指物件可能造成的影響。通常在變更shared_ptr指向的底層物件前,都會先行確認此shared_ptr是否是對該物件唯一的指標,是的話,才逕行變更。如果這個shared_ptr並不是唯一的指標,那通常就會在更動這個物件前先拷貝一個副本來應付:

if (!p.unique())

p.reset(new string(*p)); //p並不是指向物件的唯一智慧指標,因此拷貝一個原物件的副本給它

*p += newVal; //智慧指標p現在是唯一指向這個副本物件的指標,變更這個物件就不會出什麼問題——也就是,不會去影響其它共用原物件的指標



頁452

第83集9:57:00

回傳智慧指標P底層的普通指標。使用時請小心,回傳的普通指標所指的物件會在智慧指標刪除它時就消失。(參見…而且別使用智慧指標提供的成員函式get來初始化或指定另一個智慧指標)

第79集3:53:00 Returns the pointer in p. Use with caution; the object to which the returned pointer points will disappear when the smart pointer deletes it.

The smart pointer types define a function named get (described in Table 12.1 (p.452)) that returns a built-in pointer to the object that the smart pointer is managing.(頁466)



頁453

Table 12.2. Operations Specific to shared_ptr

表12.2 : shared_ptr專屬的運算

shared_ptr特有的運算

make_shared<T>(args) 第83集 8:54:00會回傳一個shared_ptr來指向型別為T的一個動態配置的物件。使用 args來初始化那個物件。

shared_ptr<T> p(q) 第83集 8:41:00

p是shared_ptr q的一個拷貝,經過這個拷貝運算後,會遞增對q所指物件的參考計數;此時q和p的成員函式use_count()回傳值都是2。q必須能夠轉型為T*(對T型別的指標) ( §4.11.2,頁162)。

p = q shared_ptr p與q其底層的指標,必須是能夠彼此轉換的。經過這樣的運算後,會遞減對p所指物件的參考計數,並遞增對q所指物件的參考計數,此時p與q將會指向同一個動態物件;如果對p先前所指物件的計數降為0,就會調用delete運算子來刪除p前所指的物件,並釋放其記憶體。



p.unique() 如果p.use_count()回傳的值是1,就回傳true,否則為false。

p.use_count() 回傳與p共用物件的指標數;可能會是緩慢的運算,主要用在除錯上。





頁465



Table 12.3. Other Ways to Define and Change shared_ptrs

表12.3 :定義shared_ptr和變更shared_ptr的其他方式

shared_ptr<T>p(q) 第83集8:23:04

p控管普通的內建型別指標q所指的物件;q必須指向由new所配置出來的動態記憶體位址,且其型別必須能轉換為T*(對於T型別的指標)。【真按:指型別轉換(type conversion)】

shared_ptr<T>p(u) 中文版把 assume翻錯了! p承繼了unique_ptr智慧指標 u對其所指動態物件的所有權;經過此運算後, u就成為一個null指標,不再指向任何物件。p assumes ownership from the unique_ptr u; makes u null.

shared_ptr<T>p(q,d) 中文版把 assume翻錯了!p承繼了內建指標q對其所指物件的所有權。q必須可轉換為T* 這種型別(對T型別的指標,§4.11.2,頁161)。p則使用可呼叫物件d (§10.3.2,頁388)來取代 delete運算子來刪除q所指的物件,以釋放其所佔用的記憶體資源。

p assumes ownership for the object to which the built-in pointer q points. q must be convertible to T* ( p. 161).

p will use the callable object d (p. 388) in place of delete to free q.

shared_ptr<T>p(p2,d) p是shared_ptr p2的一個拷貝,對其所指物件的參考計數就會+1(現在至少有p與p2指向相同的動態物件),如表12.2(頁453)中所述,只不過 p使用可呼叫物件d來取代delete。

p.reset()

p.reset(q)

p.reset(q,d) 8:56:30如果p是指向其物件的唯一一個shared_ptr,reset會摧毀其物件。如果有傳入內建指標型別的選擇性引數q,p就會指向q所指的物件(此時p、q同指一個物件,但q是普通指標(plain pointer、ordinary pointer)而已),否則就讓p變為null。若有提供可呼叫物件(callable object)d,就會用d來釋放p所指向的記憶體資源——只要與p關聯的參考計數為0;否則就會使用delete來釋放p所指向的記憶體資源。If p is the only shared_ptr pointing at its object, reset frees p's existing object. If the optional built-in pointer q is passed,makes p point to q, otherwise makes p null. If d is supplied,will call d to free q otherwise uses delete to free q.

此式其實應該就把q拷貝給p,如果有提供q的話。將q拷貝給p,則應釋放的應是p之物件佔用的記憶體,怎麼會是q呢?疑英文版已誤矣。 9:7:00 是也,英文版誤矣!9:30:00 中文版也跟著誤!

測試英文版錯誤的程式碼如下:第83集 9:50:00 真是盡信書不如無書了!唉 9:54:00

shared_ptr<int>pp(new int(3));

void process()

{

int* r = new int(10);

shared_ptr<int> ptr(r);//此時「10」這個動態物件有 普通指標r、智慧指標ptr兩個指標指向它

int* q = new int(2);

cout << "*ptr=" << *ptr << endl;

cout << "ptr計數" << ptr.use_count() << endl;

cout << "pp計數" << pp.use_count() << endl;

ptr.reset(q);//此時r指向的「10」會被ptr摧毀,r就淪為懸置指標(dangling pointer),一個活死人(活屍)

if (q == nullptr)//q仍指向「2」,不會是空指標

cout << "yes:q == nullptr" << endl;

cout << "*ptr=" << *ptr << endl;

cout << "ptr計數" << ptr.use_count() << endl;

cout << "*q=" <<*q << endl;

r = nullptr;//這樣r就不再是懸置指標了,而是一個空指標

}



int main() {

process();

cout << "*pp="<<*pp << endl;

cout << "pp計數="<<pp.use_count() << endl;

}

留言

熱門文章