C++自修入門實境秀、C++ Primer 5版研讀秀 79 ~ v12動態記憶體(dynamic memory)12.1.3. Using ...



頁464

12.1.3. Using shared_ptrs with new

如果沒有初始化一個智慧指標(smart pointer),那麼它就會被預設初始化為一個空指標 null pointer

就像表12.3所示,我們也可以用new 回傳的指標來作智慧指標的初始器

這樣當智慧指標自動釋出所指向的動態配置記憶體時,也會將new所動態配置的記憶體給釋出了

17:00

shared_ptr<double> p1; // shared_ptr that can point at a double

shared_ptr<int> p2(new int(42)); // p2 points to an int with value 42

智慧指標的建構器若有指標作為引數的,就是explicit constructor

必須使用直接初始化(direct initialize)來初始化智慧指標

shared_ptr<int> p1 = new int(1024); // error: must use direct initialization

shared_ptr<int> p2(new int(1024)); // ok: uses direct initialization

普通指標和智慧指標是不能直接(隱含)轉換的

「implicit」(隱含)在中文語境有時翻成「直接」(就是不須聲明,先斬後奏,而explicit就是必須聲明)就好

shared_ptr<int> clone(int p)

{

// ok: explicitly create a shared_ptr<int> from int*

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

}



54:20

就是因為智慧指標預定是用delete來釋放它所指向的associated物件,所以作為智慧指標初始器的普通指標也必須是指向一個動態配置記憶體的指標

57:40可見delete的應用亦有其局限,我們若要將智慧指標繫結(bind)到指向其他種類資源的指標上頭,就必須自訂出適合作為delete替代物的運算,我們將在§12.1.4(頁468)學到如何自訂我們自己的deletion: Using Our Own Deletion Code

Don’t Mix Ordinary Pointers and Smart Pointers ...

別混合使用普通指標和智慧指標…

1:8:40

There is no way to inadvertently bind the same memory to more than one independently created shared_ptr.

各自創建的智慧指標是無法指定到相同的動態配置記憶體的

3:6:37

忘了按暫停!

頁465



33:20

Table 12.3. Other Ways to Define and Change shared_ptrs

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

shared_ptr<T>p(q) p管理內建指標q所指的物件;q必須指向由new所配置的記憶體,並且必須能夠轉換為T*。【真按:指型別轉換(type conversion)】

shared_ptr<T> p(u) p預設來自unique_ptr u的所有權;使得u變為null。p assumes ownership from the unique_ptr u; makes u null.

shared_ptr<T> p(q, d) p預設內建指標q所指的物件的所有權。q必須可轉換為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 g 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的一個拷貝,如表12.2(頁453)中所述,只不過 p使用可呼叫物件d來取代delete。

p.reset()

p.reset(q)

p.reset(q, d) 如果p是指向其物件的唯一一個shared_ptr,reset會釋放 p現有的物件。如果有傳入選擇性內建指標的q,就讓p指向q,否則使p變為null。若有提供d,我們會呼叫d來釋放q,否則使用delete來釋放q。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.



這裡的「定義、變更」,其實就是指定或初始化(建構)

「可呼叫物件(callable object)」應即類似解構器(destructor)。

由此句更可見引數、參數的差別在哪裡,又各司什麼職能:

The parameter to process is passed by value, so the argument to process is copied into ptr.

3:16:50

int *x(new int(1024)); // dangerous: x is a plain pointer, not a smart pointer

process(x); // error: cannot convert int* to shared_ptr<int>

process(shared_ptr<int>(x)); // legal, but the memory will be deleted!

int j = *x; // undefined: x is a dangling pointer!

3:32:00

如果要混合用普通指標和智慧指標,當我們將管理記憶體的權責由普通指標(plain pointer、ordinary pointer)交給智慧指標後,我們就不該再用那個普通指標來試圖存取它所指向的記憶體位址(上的物件)。

普通指標也就是對內建型別(builtin type)的指標

頁466

Warning

用內建指標(built-in pointer)來試圖存取智慧指標(smart pointer)所指向的物件,是非常危險的行為,因為我們無法掌握該物件何時會被摧毀(解構)

...and Don’t Use get to Initialize or Assign Another Smart Pointer

智慧指標型別定義了一個get函式,詳Table 12.1. Operations Common to shared_ptr and unique_ptr

3:55:00

因為get函式傳回的是一個內建指標(built-in pointer),所以可以用它來初始化或指定給一個智慧指標;但建議是別這麼做

使用到get函式的程式碼必須保證不會用到delete來清除傳回指標指向的記憶體,否則傳回的指標就會淪為懸置指標(dangling pointer)了。

即使編譯器沒有顯示出錯誤,但是將另一個智慧指標和get函式成員函式回傳的內建指標繫結(bind)在一塊的行為一樣是錯誤的

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

shared_ptr<int>(q);

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

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

中文版在此翻成「int foo = *p; //未定義;p所指的記憶體會被釋放」是錯的,當作「已被釋放」

也就是說在不同的智慧指標之間不要以get回傳的內建指標來相互繫結(bind)。

上例中,p成了懸置指標(dangling pointer)。

雖然是內建指標,但因為是藉由智慧指標的get函式回傳得來的,因此delete它或者它失效後,也會連動地將它所指向的記憶體物件給註銷掉而釋出記憶體

以上有誤,要如課本所說,是因為另一個智慧指標同樣有一個獨立的參考計數(reference count),當此指標的參考計數歸零時,自然是將它所指向的記憶體物件給摧毀掉了。是以在上例中, p、q都成了懸置指標(dangling pointer),不僅是p是而已。

Other shared_ptr Operations

其他的shared_ptr運算

4:25:50

shared_ptr類別提供的運算都在:

Table 12.2. Operations Specific to shared_ptr

Table 12.3. Other Ways to Define and Change shared_ptrs 表12.3 :定義和變更shared_ptr的其他方式

reset

shared_ptr_reset

We can use reset to assign a new pointer to a shared_ptr:

p = new int(1024); // error: cannot assign a pointer to a shared_ptr

p.reset(new int(1024)); // ok: p points to a new object

參考計數(reference count)一樣在reset執行後會遞增、遞增

reset此一成員函式通常會和unique成員函式一起使用,來控制shared_ptr智慧指標們共同指向之物件的變動 5:1:30

在要對底層物件作變動時,我們會先檢查是否這個智慧指標是該物件的唯一使用者,如果不是,我們會在做變動前先複製一個複本

if (!p.unique())

p.reset(new string(*p)); // we aren't alone; allocate a new copy

*p += newVal; // now that we know we're the only pointer, okay to change this object



if (!p.unique()))

p.reset(new string(*p)); //我們並不孤獨;配置一個新的拷貝

*p += newVal; //既然我們是唯一的指標,變更這個物件就不會有問題

alone在此翻成「孤獨」非常不恰當!

可見要在一個智慧指標是唯一擁有者時才適合對其底層物件作變動

頁467

練習12.10

5:9:20

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

void process(shared_ptr<int> ptr)

{

//使用ptr

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





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

process(shared_ptr<int>(p));



出了process範疇後,shared_ptr<int>(p)會摧毀,同時因為它是智慧指標,它指向的底層物件也會被摧毀,那麼p就成了懸置指標了



這個問題應該是課本的此區段(§12.1.3)的最後所提的,既然有二個智慧指標指向同一個物件,當對底層要做處理時,就應該做一個複本才是,所以應該是要運用reset():

p.reset(new int(*p));

這樣,當process中的智慧指標摧毀時,只會摧毀這個複本,而不是影響原來的p指向的物件。又或者將p設為nullptr或用p.reset()來使其為空指標,讓它不要淪為懸置指標

總之,此題之關鍵在於如何不讓process的local智慧指標去誤刪了p所指向的底層物件。若刪除無妨,亦須將p.reset()為空指標nullptr才行,才不會成為懸置指標(dangling pointer)

練習12.11

5:41:10

process(shared_ptr<int>(p.get()));

而這個則是犯了「…而且別使用get來初始化或指定另一個智慧指標」

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

//未定義:兩個獨立的shared_ptr指向相同的記憶體

練習12.12

5:49:20

其實p和sp在這裡已是定義,不是宣告而已了declaration

sp的s就是smart指智慧指標

p就是內建指標(built-in pointer)或普通指標(plain pointer、ordinary pointer)

auto p = new int();

auto sp = make_shared<int>();

(a) process(sp);//legal,sp參考計數(reference count)會加1,proecss結束後減1。然而如練習12.10,若process內的區域智慧指標結束,要注意防止sp成為懸置指標(dangling pointer) 6:43:00 sp並非區域的,所以不會隨出process範疇而被摧毀。且process內的區域智慧指標雖在process結束後摧毀,但與sp指向同一個記憶體位置,該位置物件仍有sp指向它,故雖然proecss區域的智慧指標摧毀了,也不會釋放該物件之記憶體。

(b) process(new int());//illegal,因為型別不合,不具隱含轉型。new回傳的是一個普通指標、內建指標,而不是智慧指標shared_ptr

(c) process(p); //illegal,同前

(d) process(shared_ptr<int>(p)); //和(a)類似。這裡是將p所指向的物件操縱權交給了process內區域智慧指標,p,就合該無法再對其物件作操作了,p當設為空指標,以免淪為懸置指標。參見Table 12.3. Other Ways to Define and Change shared_ptrs 表12.3 :定義和變更shared_ptr的其他方式:shared_ptr<T>p(q)

此例中shared_ptr乃區域性的,故會被銷毀,而使p無效,成為懸置指標。

參考計數(reference count)是用use_count()成員函式來取得

6:12:00

練習12.13

auto sp = make_shared<int>();

auto p = sp.get();

delete p;

//此行可考慮改用成下式來釋放p

sp.reset(p);//俟考

普通指標p的底層物件被刪除了, sp會成為懸置指標。應該並不會連動被刪除。

在Visual Studio 2019執行到 delete p;此行時會丟出例外情形:

prog1.exe has triggered a breakpoint. occurred

6:22:28

應該是因為用delete要與new配合,p並不是new出來的,就不能用delete。是否?

6:58:59

12.1.4. Smart Pointers and Exceptions

智慧指標與例外情形

剛才才碰到例外情形。呵呵。



頁468

Using Our Own Deletion Code









p.get() 回傳P中的指標。使用時請小心,回傳的指標所指的物件會在智慧指標刪除它的時候消失。

第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)





頁463

如果想保留一個在delete後的指標,我們可以指定那個指標的值為nullptr,也就是本標題「Resetting the Value of a Pointer after a delete ...」

8:3:50

...Provides Only Limited Protection

第79集 3:00 可能有未知數量的指標同時指向了那個記憶體位置上的物件。

想要找出所有指向同一記憶體位址的指標幾乎是不可能的事

In real systems, finding all the pointers that point to the same memory is surprisingly difficult.

中文版就完全照英文原文譯,不顧中文語境。我們說中文的或寫中文的,會寫/說「是出乎意料困難的」這種詞彙嗎?

頁464

12.1.3. Using shared_ptrs with new

如果沒有初始化一個智慧指標(smart pointer),那麼它就會被預設初始化為一個空指標 null pointer

就像表12.3所示,我們也可以用new 回傳的指標來作智慧指標的初始器

這樣當智慧指標自動釋出所指向的動態配置記憶體時,也會將new所動態配置的記憶體給釋出了

17:00

shared_ptr<double> p1; // shared_ptr that can point at a double

shared_ptr<int> p2(new int(42)); // p2 points to an int with value 42

智慧指標的建構器若有指標作為引數的,就是explicit constructor

必須使用直接初始化(direct initialize)來初始化智慧指標

shared_ptr<int> p1 = new int(1024); // error: must use direct initialization

shared_ptr<int> p2(new int(1024)); // ok: uses direct initialization

普通指標和智慧指標是不能直接(隱含)轉換的

「implicit」(隱含)在中文語境有時翻成「直接」(就是不須聲明,先斬後奏,而explicit就是必須聲明)就好

shared_ptr<int> clone(int p)

{

// ok: explicitly create a shared_ptr<int> from int*

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

}



54:20

就是因為智慧指標預定是用delete來釋放它所指向的associated物件,所以作為智慧指標初始器的普通指標也必須是指向一個動態配置記憶體的指標

57:40可見delete的應用亦有其局限,我們若要將智慧指標繫結(bind)到指向其他種類資源的指標上頭,就必須自訂出適合作為delete替代物的運算,我們將在§12.1.4(頁468)學到如何自訂我們自己的deletion: Using Our Own Deletion Code

Don’t Mix Ordinary Pointers and Smart Pointers ...

別混合使用普通指標和智慧指標…

1:8:40

There is no way to inadvertently bind the same memory to more than one independently created shared_ptr.

各自創建的智慧指標是無法指定到相同的動態配置記憶體的

3:6:37

忘了按暫停!

頁465



33:20

Table 12.3. Other Ways to Define and Change shared_ptrs

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

shared_ptr<T>p(q) p管理內建指標q所指的物件;q必須指向由new所配置的記憶體,並且必須能夠轉換為T*。【真按:指型別轉換(type conversion)】

shared_ptr<T> p(u) p預設來自unique_ptr u的所有權;使得u變為null。p assumes ownership from the unique_ptr u; makes u null.

shared_ptr<T> p(q, d) p預設內建指標q所指的物件的所有權。q必須可轉換為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 g 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的一個拷貝,如表12.2(頁453)中所述,只不過 p使用可呼叫物件d來取代delete。

p.reset()

p.reset(q)

p.reset(q, d) 如果p是指向其物件的唯一一個shared_ptr,reset會釋放 p現有的物件。如果有傳入選擇性內建指標的q,就讓p指向q,否則使p變為null。若有提供d,我們會呼叫d來釋放q,否則使用delete來釋放q。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.



這裡的「定義、變更」,其實就是指定或初始化(建構)

「可呼叫物件(callable object)」應即類似解構器(destructor)。

由此句更可見引數、參數的差別在哪裡,又各司什麼職能:

The parameter to process is passed by value, so the argument to process is copied into ptr.

3:16:50

int *x(new int(1024)); // dangerous: x is a plain pointer, not a smart pointer

process(x); // error: cannot convert int* to shared_ptr<int>

process(shared_ptr<int>(x)); // legal, but the memory will be deleted!

int j = *x; // undefined: x is a dangling pointer!

3:32:00

如果要混合用普通指標和智慧指標,當我們將管理記憶體的權責由普通指標(plain pointer、ordinary pointer)交給智慧指標後,我們就不該再用那個普通指標來試圖存取它所指向的記憶體位址(上的物件)。

普通指標也就是對內建型別(builtin type)的指標

頁466

Warning

用內建指標(built-in pointer)來試圖存取智慧指標(smart pointer)所指向的物件,是非常危險的行為,因為我們無法掌握該物件何時會被摧毀(解構)

...and Don’t Use get to Initialize or Assign Another Smart Pointer

智慧指標型別定義了一個get函式,詳Table 12.1. Operations Common to shared_ptr and unique_ptr

3:55:00

因為get函式傳回的是一個內建指標(built-in pointer),所以可以用它來初始化或指定給一個智慧指標;但建議是別這麼做

使用到get函式的程式碼必須保證不會用到delete來清除傳回指標指向的記憶體,否則傳回的指標就會淪為懸置指標(dangling pointer)了。

即使編譯器沒有顯示出錯誤,但是將另一個智慧指標和get函式成員函式回傳的內建指標繫結(bind)在一塊的行為一樣是錯誤的

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

shared_ptr<int>(q);

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

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

中文版在此翻成「int foo = *p; //未定義;p所指的記憶體會被釋放」是錯的,當作「已被釋放」

也就是說在不同的智慧指標之間不要以get回傳的內建指標來相互繫結(bind)。

上例中,p成了懸置指標(dangling pointer)。

雖然是內建指標,但因為是藉由智慧指標的get函式回傳得來的,因此delete它或者它失效後,也會連動地將它所指向的記憶體物件給註銷掉而釋出記憶體

以上有誤,要如課本所說,是因為另一個智慧指標同樣有一個獨立的參考計數(reference count),當此指標的參考計數歸零時,自然是將它所指向的記憶體物件給摧毀掉了。是以在上例中, p、q都成了懸置指標(dangling pointer),不僅是p是而已。

Other shared_ptr Operations

其他的shared_ptr運算

4:25:50

shared_ptr類別提供的運算都在:

Table 12.2. Operations Specific to shared_ptr

Table 12.3. Other Ways to Define and Change shared_ptrs 表12.3 :定義和變更shared_ptr的其他方式

reset

shared_ptr_reset

We can use reset to assign a new pointer to a shared_ptr:

p = new int(1024); // error: cannot assign a pointer to a shared_ptr

p.reset(new int(1024)); // ok: p points to a new object

參考計數(reference count)一樣在reset執行後會遞增、遞增

reset此一成員函式通常會和unique成員函式一起使用,來控制shared_ptr智慧指標們共同指向之物件的變動 5:1:30

在要對底層物件作變動時,我們會先檢查是否這個智慧指標是該物件的唯一使用者,如果不是,我們會在做變動前先複製一個複本

if (!p.unique())

p.reset(new string(*p)); // we aren't alone; allocate a new copy

*p += newVal; // now that we know we're the only pointer, okay to change this object



if (!p.unique()))

p.reset(new string(*p)); //我們並不孤獨;配置一個新的拷貝

*p += newVal; //既然我們是唯一的指標,變更這個物件就不會有問題

alone在此翻成「孤獨」非常不恰當!

可見要在一個智慧指標是唯一擁有者時才適合對其底層物件作變動

頁467

練習12.10

5:9:20

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

void process(shared_ptr<int> ptr)

{

//使用ptr

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





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

process(shared_ptr<int>(p));



出了process範疇後,shared_ptr<int>(p)會摧毀,同時因為它是智慧指標,它指向的底層物件也會被摧毀,那麼p就成了懸置指標了



這個問題應該是課本的此區段(§12.1.3)的最後所提的,既然有二個智慧指標指向同一個物件,當對底層要做處理時,就應該做一個複本才是,所以應該是要運用reset():

p.reset(new int(*p));

這樣,當process中的智慧指標摧毀時,只會摧毀這個複本,而不是影響原來的p指向的物件。又或者將p設為nullptr或用p.reset()來使其為空指標,讓它不要淪為懸置指標

總之,此題之關鍵在於如何不讓process的local智慧指標去誤刪了p所指向的底層物件。若刪除無妨,亦須將p.reset()為空指標nullptr才行,才不會成為懸置指標(dangling pointer)

練習12.11

5:41:10

process(shared_ptr<int>(p.get()));

而這個則是犯了「…而且別使用get來初始化或指定另一個智慧指標」

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

//未定義:兩個獨立的shared_ptr指向相同的記憶體



練習12.12

5:49:20

其實p和sp在這裡已是定義,不是宣告而已了declaration

sp的s就是smart指智慧指標

p就是內建指標(built-in pointer)或普通指標(plain pointer、ordinary pointer)

auto p = new int();

auto sp = make_shared<int>();

(a) process(sp);//legal,sp參考計數(reference count)會加1,proecss結束後減1。然而如練習12.10,若process內的區域智慧指標結束,要注意防止sp成為懸置指標(dangling pointer) 6:43:00 sp並非區域的,所以不會隨出process範疇而被摧毀。且process內的區域智慧指標雖在process結束後摧毀,但與sp指向同一個記憶體位置,該位置物件仍有sp指向它,故雖然proecss區域的智慧指標摧毀了,也不會釋放該物件之記憶體。

(b) process(new int());//illegal,因為型別不合,不具隱含轉型。new回傳的是一個普通指標、內建指標,而不是智慧指標shared_ptr

(c) process(p); //illegal,同前

(d) process(shared_ptr<int>(p)); //和(a)類似。這裡是將p所指向的物件操縱權交給了process內區域智慧指標,p,就合該無法再對其物件作操作了,p當設為空指標,以免淪為懸置指標。參見Table 12.3. Other Ways to Define and Change shared_ptrs 表12.3 :定義和變更shared_ptr的其他方式:shared_ptr<T>p(q)

此例中shared_ptr乃區域性的,故會被銷毀,而使p無效,成為懸置指標。

參考計數(reference count)是用use_count()成員函式來取得

6:12:00

練習12.13

auto sp = make_shared<int>();

auto p = sp.get();

delete p;

//此行可考慮改用成下式來釋放p

sp.reset(p);//俟考

普通指標p的底層物件被刪除了, sp會成為懸置指標。應該並不會連動被刪除。

在Visual Studio 2019執行到 delete p;此行時會丟出例外情形:

prog1.exe has triggered a breakpoint. occurred

6:22:28

應該是因為用delete要與new配合,p並不是new出來的,就不能用delete。是否?

6:58:59

12.1.4. Smart Pointers and Exceptions

智慧指標與例外情形

剛才才碰到例外情形。呵呵。



頁468

Using Our Own Deletion Code









p.get() 回傳P中的指標。使用時請小心,回傳的指標所指的物件會在智慧指標刪除它的時候消失。

第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)





頁463

如果想保留一個在delete後的指標,我們可以指定那個指標的值為nullptr,也就是本標題「Resetting the Value of a Pointer after a delete ...」

8:3:50

...Provides Only Limited Protection

第79集 3:00 可能有未知數量的指標同時指向了那個記憶體位置上的物件。

想要找出所有指向同一記憶體位址的指標幾乎是不可能的事

In real systems, finding all the pointers that point to the same memory is surprisingly difficult.

中文版就完全照英文原文譯,不顧中文語境。我們說中文的或寫中文的,會寫/說「是出乎意料困難的」這種詞彙嗎?


留言

熱門文章