C++自修入門實境秀、C++ Primer 5版研讀秀 90 ~12.3.1. Design of the Query Program~練習12...



頁473

12.1.6. weak_ptr

第90集 1:00

weak這裡或許可以翻成「柔性的、軟式的、鴿派的、非強制性的、可有可無的」,果然,它不會去決定(判定)它所指物件的「生死」:

A weak_ptr (Table 12.5) is a smart pointer that does not control the lifetime of the object to which it points.

隨著shared_ptr 參考計數歸零或shared_ptr都消亡後,該物件也消亡,而指向該物件的所有的weak_ptr卻仍會存在,不會隨之消亡。由此意義上,才叫做weak_ptr。它與它指向的物件是一種弱關聯的關係。如此不就成了懸置指標了?所以才有一個expired和lock這兩個成員函式(方法)來測試它是否是個「懸置指標」。

That object will be deleted even if there are weak_ptrs pointing to it—hence the name weak_ptr, which captures the idea that a weak_ptr shares its object “weakly.”

就是「分一杯羹」的概念,有也好、沒有也沒差的。其實C++設計這個weak_ptr可能就是想在shared_ptr與unique_ptr之間取得一個中性的、溫和的平衡物。

Table 12.5. weak_ptrs

第90集8:15

表 12.5 : weak_ptr

weak_ptr<T>w 一個指向型別為T、其值為null的 weak_ptr物件。也就是weak_ptr與shared_ptr、unique_ptr一樣,預設初始化都是空指標。1:58:00也就是智慧指標預設初始化都是空指標。

weak_ptr<T>w(sp) 一個指向型別為T、且由shared_ptr sp來初始化的weak_ptr,其指向的物件與sp是相同的。T和sp所指的型別必須有轉換關係。

w = p p可以是一個shared_ptr或weak_ptr。指定assing之後,w會軟性地與p共享所有權。可見可以將一個shared_ptr直接指定給一個weak_ptr,而weak_ptr彼此間也有拷貝與指定的運算,不像unique_ptr。

w.reset() 讓w變為null。就是歸零一個weak_ptr。第90集22:00 在Visual Studio 2019 測試發現沒有定義「w = nullptr;」這樣的運算。weak_ptr它有指定之運算,但右運算元就卻不能是nullptr。

E0349 no operator "=" matches these operands

Error C2679 binary '=': no operator found which takes a right-hand operand of type 'nullptr' (or there is no acceptable conversion)由此句,似乎weak_ptr不能是空指標nullptr。所以weak_ptr歸零一定要用這個。

w.use_count() 與w共享所有權的shared_ptr數目。可見use_count的回傳值是shared_ptr專用的,就是「參考計數」。參考計數與weak_ptr是無關的。

w.expired() 如果w.use_count()是零,就回傳true,否則為false。

use_count()回傳零,就表示shared_ptr都消亡了,shared_ptr都沒了,就表示由shared_ptr托管的物件也沒了。

w.lock() 如果expired()回傳ture,就回傳一個null shared_ptr,否則就會回傳一個指向這個weak_ptr w所指物件的shared_ptr。就是回傳的shared_ptr指向的會是和w指向的是同一個物件。

shared_ptr<int> sp(new int(1));

weak_ptr<int> w(sp);

sp.reset(); //sp成了空指標

cout << w.expired() << endl; //print: 1

cout << w.lock() << endl; //print:00000000

w.reset(); //w成了空指標(並不能用「w=nullptr」來指定w為空指標!)

auto p = sp.get(); //p還是空指標(sp已是空指標)

if (p == nullptr)

cout << "p==nullptr" << endl; //printed

delete p; //對空指標delete等於白做工

p = nullptr; //此時p早已是空指標,這樣做只是多此一舉、疊床架屋而已

weak_ptr的建構是由shared_ptr來初始化的

auto p = make_shared<int>(42);

weak_ptr<int>wp(p); // wp weakly shares with p; use count in p is unchanged

36:44 50:00

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

weak_ptr<int> wp(p);

void s(shared_ptr<int> np = wp.lock())

{

cout << wp.use_count() << endl; //print:2

cout << p.use_count() << endl; //print:2

cout << p.unique() << endl; //print:0

cout << np.unique() << endl; //print:0

cout << np.use_count() << endl; //print:2

}

可見np與sp是等同於「指定」或「拷貝」的結果的,並不是那種各自獨立宣告的shared_ptr

Checked Pointer Class

其指標有經過檢查的指標類別

「能/會主動、自主性地檢查其指標有效性的類別」或者

「能/會主動、自主性地檢查其自身指標有效性的指標類別

58:00

改中文版作文:

12.1.6 weak_ptr

weak_ptr (表12.5)是一種智慧指標,但它對於它所指向的物件生命並沒有佔有慾(它對它指向的物件生命週期並沒有興趣)。可以說,unique_ptr是佔有慾最強的,而shared_ptr次之(它懂得分享——既以為人己愈有,既以與人己愈多);weak_ptr則最不管事(可以說與窘類別很像)。因此,weak_ptr所指物件的生死是交由某個shared_ptr代為控管的。也因此,將一個weak_ptr和shared_ptr繫結並不會改變那個shared_ptr的參考計數,也就是說,一旦指向該物件的最後一個shared_ptr消失,不管還有多少weak_ptr指向著它,那個物件本身就會被shared_ptr調用delete或刪除器刪除。正因為weak_ptr這樣的特性,所以它才會叫做「weak」_ptr,這也透露了它在共用物件時是「不強勢(弱性weakly)」的。

weak_ptr既名為智慧指標,因此,當我們創建一個weak_ptr的同時,我們就會用一個shared_ptr作為初始器來將它初始化,以便它所指向的物件能得到有效地控管:

auto p = make_shared<int>(42);

weak_ptr<int> wp(p); // wp雖共享了shared_ptr p所指向的物件,但並不會干涉p的參考計數及該物件的生命長度;p中的使用計數use count(即參考計數(reference count),即成員函式use_count()所回傳的值)並不會因有多少個weak_ptr與它共享物件而改變(wp weakly shares with p; use count in p is unchanged)

這裡wp和p都指向同樣的物件。因為這個共用非是強勢的(軟性、柔性、弱性)的,所以在創建wp的同時並不會動到p的計數參考;而wp所指的物件也有可能在它還指向那些物件時就被p刪除了。

正因為如此,雖然還有weak_ptr存在,但其所指向的物件可能已不復存在(也就是weak_ptr都成了呆指標—懸置指標了),因此並不適合冒然使用一個weak_ptr來存取其所指的物件。若欲存取該物件,應須先呼叫lock成員函式來確定該物件是否還存在。lock函式就會查看weak_ptr所指的物件是否依然存在。(其實也可以用expired成員函式來判斷)若還存在,lock就會回傳一個shared_ptr來指向那個存在的物件。這個回傳的shared_ptr就跟任何其他的shared_ptr一樣,都能保證只要shared_ptr的參考計數非0,它所指向的底層物件就沒有被刪之虞,會一直存在到所有的shared_ptr都消失為止。舉例來說:

if(shared_ptr<int> np = wp.lock()){// 如果 np 不是 null 就為 true

//在if內,np會與wp共用其物件(英文版將wp作p,不太適當。p還在前面,是用來初始化wp的shared_ptr,但若單看這裡的程式碼範例,會不明所以。)

}

以上程式只會在lock回傳的不是null時,才會進入if本體,因此在if本體內,就能夠放心地使用np來存取np與wp所指向的那個物件。

Table 12.5. weak_ptrs

第90集1:9:59

指標有效性是經過檢查的指標類別(具備檢查check機制的指標類別)

什麼叫做「指標類別」也不過就是它的成員函式支援/提供了如同指標一般的運算,如StrBlobPtr中的incr、deref二個成員函式就是模擬/模仿內建型別(builtin type)指標的「++、*」運算子的功能,或者說,利用了「++、*」運算子的功能來達到所謂的指標類別該提供的性能——諸如遞增(推進)和解參考等的操作/運算。只要一個類別具有這樣功能的成員函式,大概就可視之為指標類別了。

所以類別叫/是「抽象資料結構」只要其資料(此資料是指類別內的內容、內容物、所含物)結構符合某些特性或具備哪些功能的,就屬哪一種類別了。

為了示範weak_ptr是幹什麼用的、用weak_ptr又有什麼好處、有什麼必要?我們可以為我們曾定義過的StrBlob類別再定義一個與它相伴的指標類別(companion pointer class)。我們將這個指標類別,命名為StrBlobPtr,它將會儲存一個由StrBlob的data這個shared_ptr型別的資料成員來初始化的weak_ptr(也就是由shared_ptr來初始化weak_ptr),這樣一來這個weak_ptr也就會和StrBlob中data這個shared_ptr型別的資料成員指向同一個vector;而且也不會影響這個data的參考計數。也就是與StrBlob中的這個shared_ptr(名為data)共享其所指vector物件。也因此,就可以藉由weak_ptr獨有的lock與expired運算來判斷這個vector物件還存不存在。

頁474

class StrBlobPtr

參考Defining the StrBlob Class 頁456

唯StrBlob必須加上以下這行:

friend class StrBlobPtr;//頁269-270,279-280;不加「class」的就會當作函式編譯

這裡的blob意思當是這個:

二進位大型物件- 维基百科,自由的百科全书

二進位大型物件(英語:binary large object ,或英語:basic large object,縮寫為Blob、BLOB、BLOb),在資料庫管理系統中,將二進位資料儲存為一個單一個體的集合 ...

Blob (物件) 儲存體簡介- Azure 儲存體| Microsoft Docs

https://docs.microsoft.com › storage › blobs › storage-blobs-introduction

Azure Blob 儲存體可儲存大量的非結構化物件資料,例如文字或二進位資料。 Azure Blob 儲存體具有高度擴充性與可用性。 用戶端可從PowerShell ...

可見有時不能光查字典,得看看一般文章中或參考資料裡,人家是怎麼用該字的 ⑨對錯問題,回到二作

須複習參考前面有關friend(建立朋友關係)的論述

// StrBlobPtr會在試著存取一個不存在的元素時擲出一個例外

class StrBlobPtr

{

public:

StrBlobPtr() : curr(0) {}

StrBlobPtr(StrBlob& a, size_t sz = 0) : wptr(a.data), curr(sz) {}

std::string& deref() const;

StrBlobPtr& incr(); // 前缀版本(prefix version)

private:

//如果檢查成功,check會回傳一個shared_ptr指向vector

std::shared_ptr<std::vector<std::string>>

check(std::size_t, const std::string&) const;

//儲存一個weak_ptr,這表示底層的vector可能被摧毁了

// store a weak_ptr, which means the underlying vector might be destroyed

std::weak_ptr<std::vector<std::string>> wptr;

std::size_t curr; //在陣列中的目前位置// current position within the array

};



std::shared_ptr<std::vector<std::string>>

StrBlobPtr::check(std::size_t i, const std::string &msg)

const

{

auto ret = wptr.lock(); // is the vector still around?

if (!ret)

throw std::runtime_error("unbound StrBlobPtr");

if (i >= ret->size())

throw std::out_of_range(msg);

return ret; // otherwise, return a shared_ptr to the vector

}

這裡就可理解weak_ptr中的lock成員函式的意思就是weak_ptr有沒有對應到的物件,有的話,就會lock起來(和該物件繫結(bind))。

改中文版作文:

因為是使用了weak_ptr,而不是用shared_ptr,所以不會去影響到StrBlob類別物件指向的那個vector的生命週期。也可以由此防止使用者試圖去存取一個不再存在的幽靈vector。因為不再存在的物件可以藉由weak_ptr的lock成員函式來加以檢查。

StrBlobPtr會有兩個資料成員:一個是wptr,它要嘛是一個null值的空指標,要嘛就是一個指向了一個vector的weak_ptr,這個vector就是在建構這個StrBlobPtr時所帶入的StrBlob型別物件中的data成員所指向的那個;另一個則是curr,它是這個vector物件的索引值,代表目前所在的元素位置。我們的這個指標類別StrBlobPtr就像它的伙伴類別(companion classStrBlobPtr的StrBlob伴隨類別companion pointer class;StrBlob則是StrBlobPtr的伙伴類別)StrBlob,也會有一個check成員函式來確保解參考StrBlobPtr是安全的:第90集 1:46:25

StrBlobPtr的定義(or定義StrBlobPtr)

// 當企圖存取一個不存在的vector元素時,StrBlobPtr就會丟出一個例外:

class StrBlobPtr {

public:

StrBlobPtr () : curr(0){ }//第1個建構器(也是預設建構器(default constructor)——沒有引數)

StrBlobPtr(StrBlob &a, size_t sz = 0):

wptr(a.data), curr(sz){ } //第2個建構器

std::string& deref() const;

StrBlobPtr& incr() ; // 前缀版本的遞增++ (prefix version)

private:

//只要通過檢查,check就會回傳一個指向vector的shared_ptr

std::shared_ptr<std::vector<std::string>>

check(std::size_t, const std::string&) const;

// wptr儲存一個weak_ptr,利用它來作為一個底層vector可能已摧毁的指示器(指標;這個智慧指標不是用來管控其所指之物的生死存廢,而是用來檢查其所指物件是否還存在)

// wptr store a weak_ptr, which means the underlying vector might be destroyed

std::weak_ptr<std::vector<std::string>> wptr;

std::size_t curr; // curr是用來指示vector中目前元素的位置// current position within the array——應是英文版筆誤!

};

預設的建構器會產生一個null的StrBlobPtr;因為沒有提供初始器,weak_ptr就會預設初始化為空指標。(實際上是「wptr」才是空指標)它的建構器初始器串列(§ 7.1.4,頁265)會明確地將curr初始化為零,並隱含地將wptr初始化為一個null的weak_ptr。就是預設初始化。第二個建構器則接受二個引數傳入,一個是對StrBlob的參考,另一個則是選擇性的引數,用來指向vector中元素的索引值。這個建構器將wptr初始化為指向用來建構這個StrBlobPtr的StrBlob物件裡shared_ptr data成員指向的vector,並用sz的值來將curr初始化。這裡,我們使用了一個預設引數「0」(§6.5.1,頁236)來將curr預設初始化為代表vector中第一個元素的位置(即索引值)。我們將看到,StrBlob的end成員函式會用得上sz這個參數。

值得注意的是,我們無法將一個StrBlobPtr繫結至一個const StrBlob物件。這種限制只是因為在定義StrBlobPtr的第二個建構器時,將它所能接受的引數,設定為是對型別為非const的StrBlob物件的參考,而不是對const的StrBlob物件的參考,所以在創建這樣的StrBlobPtr物件時,只能傳入非常值not const的StrBlob引數。


第90集2:11:00

StrBlobPtr的check成員函式也與StrBlob中的那個不同,因為它會檢查它所指的vector是否仍然存在:

// StrBlobPtr的成員函式check會傳回一個shared_ptr的智慧指標,這個指標指向的是元素型別為string的vector物件

std::shared_ptr<std::vector<std::string>>

StrBlobPtr::check(std::size_t i, const std::string &msg) const

{

auto ret = wptr.lock();// 用ret(即retrun)區域變數來存放Weak_ptr這個型別的StrBlobPtr資料成員wptr它叫用成員函式lock回傳的結果,這個結果是一個shared_ptr型別的指標;用這個表達式(運算式express)來測度wptr所指向的vector物件是否還存在。wptr是由StrBlob資料成員data來初始化的,也就是與shared_ptr型別的data指向的是同一個物件,在此這個物件是vector

if(!ret) //如果傳回來的指標為null值(即空指標),便等於「0」,條件式就會成立(在C++中true=1,false=0)

throw std::runtime_error("unbound StrBlobPtr");//要用「runtime_error」要記得「#include <stdexcept>」。「"unbound StrBlobPtr"」表示調用這個check的StrBlobPtr型別物件(它是一個指標型別)並沒有指向任何物件(在這裡這個物件是vector;而不是它的元素!)——沒有和任何物件繫結(bind)在一塊;而StrBlob的資料成員、型別為shared_ptr的data,指向是同一個vector(之前我弄錯了,要改!)2:20:00

if(i >= ret->size())

throw std::out_of_range(msg);

return ret; // 只要通過了以上兩個if條件式的檢驗,就可以放心回傳那個由wptr調用lock成員函式回傳的shared_ptr,這個shared_ptr和wptr是指向同一個的vector,也和StrBlob中的data指向同一個。即資源共享。//要用「out_of_range」也要記得「#include <stdexcept>」。「ret」應即「return」的縮寫

}

2:22:40 第90集

頁475

Pointer Operations

std::string &StrBlobPtr::deref() const//參見練習12.21

{

auto p = check(curr, "dereference past end");

return (*p)[curr]; // (*p) is the vector to which this object points

}

第90集2:25:00因為StrBlobPtr所具的資料(個性)及運算、操縱(行為模式)都如指標所具之特性,故謂之指標類別。

指標就是可以解參考後取得其所指之物件。

指標就是可以遞增(推進)或遞減(倒退)的物件:(可見『指標』就是『指示標識、指示牌』的省稱

// prefix: return a reference to the incremented object

StrBlobPtr &StrBlobPtr::incr()

{

// if curr already points past the end of the container, can't increment it

check(curr, "increment past end of StrBlobPtr");

++curr; // advance the current state

return *this;

}

看吧!這時才出現end!

這裡pdf舊版又與google圖書版不同了:

Of course, in order to access the data member, our pointer class will have to be a friend of StrBlob (§ 7.3.4, p. 279). We’ll also give our StrBlob class begin and end operations that return a StrBlobPtr pointing to itself:

新版(google圖書版):

We’ll also give our StrBlob class begin and end operations. These members will return StrBlobPtrs pointing to the first or one past the last element in the StrBlob itself. In addition, because StrBlobPtr accesses the data member of StrBlob , we must also make StrBlobPtr a friend of StrBlob (§ 7.3.4 , p. 279 ):

這裡「we must also make」原文未空格,成了「wemust alsomake」

這裡中文版是照新版翻的。



可見「StrBlob」是藉由程式庫型別(library type)vector來建構的一個自訂容器(尤其是由動態配置記憶體配置的一個容器

程式碼也不同,此乃pdf舊版:

// forward declaration needed for friend declaration in StrBlob

class StrBlobPtr; //可以重複宣告,卻不能重複定義;若無此行,則StrBlob中成員函式用到StrBlobPtr都會在編譯時期出錯「use of undefined type 'StrBlobPtr'」

class StrBlob

{

friend class StrBlobPtr;

// other members as in § 12.1.1 (p. 456)

// return StrBlobPtr to the first and one past the last elements

StrBlobPtr begin() { return StrBlobPtr(*this); }//都是用第2個建構器(constructor),此乃用預設引數(default argument),這裡this是StrBlob!

StrBlobPtr end()

{

auto ret = StrBlobPtr(*this, data->size());

return ret;

}

};

完整的程式碼要看這裡:page474_class_StrBlobPtr 10:14:00

如果直接在StrBlobr中用「StrBlobPtr」作為定義(函式的主體內容),則會出如上開之錯

新版的(google圖書)就依此作出了訂正:

class StrBlob

{

friend class StrBlobPtr;

// other members as in § 12.1.1 (p. 456)

StrBlobPtr begin(); // return StrBlobPtr to the first element

StrBlobPtr end(); // and one past the last element

};

// these members can’t be defined until StrStrBlob and StrStrBlobPtr are defined

StrBlobPtr StrBlob::begin() { return StrBlobPtr(*this); }

StrBlobPtr StrBlob::end() { return StrBlobPtr(*this, data->size()); }

結果「StrStrBlob and StrStrBlobPtr」還是拼錯,可見新版出版之急就章。當作「StrBlob and StrBlobPtr」!

改中文版作文:

第90集 2:47:30

因為weak_ptr並不會干涉(participate)與它共享vector的shared_ptr之參考計數,所以這個StrBlobPtr指向的vector有可能會被Shared_ptr刪除。只要這個vector不在了,那麼在weak_ptr物件wptr上呼叫lock成員函式就會回傳一個null(空的指標、空指標,其型別卻是shared_ptr)。當vector不在了,參考到這個vector的任何操作當然都會失敗。所以在經成員函式check檢查過後,就必須丟出個例外狀況來作反應,並中止呼叫check端的程式。如果lock回傳的並不是null,check成員函式就會接著檢驗傳給它的索引值引數:如果這個引數值小於容器的大小(元素的數量)就表示是有有效的索引值,check就會回傳它從lock獲得的shared_ptr,並結束check負責的檢查指標有效性的工作。

指標運算

2:57:47這就關係到為什麼StrBlobPtr是一個指標類別了,因為其類別具備了指標的運算性能、具有指標的屬性

我們會在第14章學到如何定義我們自己的運算子。而現在,我們則會透過定義名為deref與incr這兩個成員函式來讓StrBlobPtr型別的指標物件具備解參考和遞增運算。可見運算子仍是函式的一種,也是負責在做某些操作/運算的,所以才能由函式來代替。

deref成員函式會先調用check來幫忙檢查指向vector的指標是否有效,且curr這個作為該vector索引值的資料成員(data member;field)的值,是否也是個有效的索引值並沒有超出能對vector作下標(subscript)運算的大小,以便作為對vector下標運算之引數:

std::string& StrBlobPtr::deref() const

{

auto p = check(curr, "dereference past end");

return (*p)[curr]; // (*p):解參考p後得到的是check回傳的shared_ptr指標物件p它所指向那個的vector,再對這個vector作下標運算,取得其元素值,即string型別的元素值,來回傳對它的參考(傳址、左值)

}3:11:50

如果通過了check的檢驗而沒有丟出例外,p就會接收check回傳的值,這個值就是一個shared_ptr,這個shared_ptr指向這個StrBlobPtr物件所指的vector(也就是這個vector是由這個shared_ptr與這個調用deref的StrBlobPtr型別物件共享;而且就是在建構這個StrBlobPtr時傳入的那個StrBlob的data成員所指的vector)這個「(*p)[curr]」運算式會先解參考那個shared_ptr來取得vector,並對這個取得的vector使用下標運算子來擷取其對應的元素(即位於curr這個索引值位置上的元素),作為deref的回傳左值。

std::string& StrBlobPtr::deref() const

{

auto p = check(curr, "dereference past end");

return (*p)[curr]; // (*p) is the vector to which this object points

/*真正「推進」元素是在這行,不是在incr(),incr()只是「推進」索引值而已。再在此deref()先檢查

索引值curr有效否,有效才「真的」「推進」(其實是對vector的「下標運算」!)

故反而是在解參考(即deref())時才「推進」元素(實即對vector下標爾),而不是在incr()就推進了

真正有「推進」的,只有索引值,而元素並未被「推進」,只是vector被下標(subscript)而已*/

}

incr成員函式也會呼叫check來先作檢查:

//前綴:回傳指向遞增過的物件的一個參考。英文版這樣說會誤導人,其實這裡StrBlobPtr並沒有真的遞增(推進)過,只是它的資料成員curr遞增而已。然後再用這個curr作為索引值來對StrBlobPtr所指向的vector來下標,如是而已(其實並未遞增——推進!)

StrBlobPtr& StrBlobPtr: :incr ()

{

//如果curr已經指向超過容器尾端的地方,就無法遞增它

check(curr, "increment past end of StrBlobPtr");

++ curr;//作為模擬推進目前元素位置用的數據——其實只有作為下標運算子([]運算子,subscript operator)的運算元——也就是vector的索引值——遞增(加1)而已

return *this;

}




3:22:40

StrBlobPtr& StrBlobPtr::incr()

{

// if curr already points past the end of the container, can't increment it

/*若如課本,只寫「=」後面的表達式,那麼編譯器會出現這樣的警告訊息:

Severity Code Description Project File Line Suppression State

Warning C26444 Avoid unnamed objects with custom construction and destruction (es.84). prog1 V:\PROGRAMMING\C++\OSCARSUN72\PROG1\PROG1\STRBLOB.CPP 85

*/

//auto sp = check(curr, "increment past end of StrBlobPtr");

shared_ptr<vector<string>> sp = check(curr, "increment past end of StrBlobPtr");

++curr; // for advancing the current state

return *this;

}

第90集3:24:45

有了StrBlobPrt之後,我們也可以再給我們的StrBlob類別提供額外的兩個begin和end的運算——即兩個成員函式支援這樣的運算/操作。begin會回傳一個指向StrBlob(其實質是假借vector來作的容器)中的第一個元素的StrBlobPtr,而end則是回傳一個指向StrBlob最後一個元素後面那個位置的StrBlobPtr。要完善StrBlob與StrBlobPtr這兩個伙伴類別的關係,除了這些設計之外,因為StrBlobPtr會用到StrBlob的私有(private:)資料成員data,所以我們就必須得將StrBlobPtr指定為StrBlob的一個friend( § 7.3.4 ,頁279)可見伙伴類別與friend息息相關:

class StrBlob {

friend class StrBlobPtr; //若是函式,就不必加「class」。class是註明後面接著的是類別名稱,而不是函式名稱。

//其他的成員內容跟§12.1.1(頁456)中一樣

StrBlobPtr begin();//回傳StrBlobPtr,它是指向StrBlob中vector的第一個元素

StrBlobPtr end() ;//回傳StrBlobPtr,它指向的是StrBlob中vector的最後元素後面的那個位置

};

//這些成員可以先宣告,但必須在StrStrBlob和StrStrBlobPtr定義好之後才能加以定義

StrBlobPtr StrBlob::begin(){ return StrBlobPtr(*this);}

StrBlobPtr StrBlob::end{){ return StrBlobPtr(*this, data->size()); }

頁476

練習12.19

第90集3:33:30

自訂您版本的StrBlobPtr,並且使用適當的friend宣告、和定義begin、end這兩個成員函式來更新您先前那個StrBlob類別的定義。

參見前面練習12.2

標頭檔

#ifndef STRBLOB_H

#define STRBLOB_H

#include<string>

#include<vector>

#include<memory>



//class StrBlobPtr;//可以重複宣告,卻不能重複定義;若無此行,則StrBlob中成員函式用到StrBlobPtr都會在編譯時期出錯「use of undefined type 'StrBlobPtr'」

class StrBlob

{

friend class StrBlobPtr;//頁269-270,279-280;不加「class」的就會當作函式編譯

public:

typedef std::vector<std::string>::size_type size_type; //以型別別名定義型別成員(type member)

StrBlob(); //建構器

StrBlob(std::initializer_list<std::string> il); //帶了一個initializer_list<string>參數的建構器

size_type size() const { return data->size(); }//常值的const成員函式

bool empty() const { return data->empty(); } //常值的const成員函式

// add and remove elements

void push_back(const std::string& t) {

data->push_back(t);

}

void pop_back();

// element access

std::string& front();

std::string& back();



// return StrBlobPtr to the first and one past the last elements

StrBlobPtr begin();

StrBlobPtr end();



private:

std::shared_ptr<std::vector<std::string>> data;

// throws msg if data[i] isn't valid

void check(size_type i, const std::string& msg) const;

};

//伙伴類別似乎是要放在同一個標頭檔中!!否則編譯(建置)時會出錯。 https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/compiler-error-c2027?f1url=https%3A%2F%2Fmsdn.microsoft.com%2Fquery%2Fdev16.query%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(C2027)%26rd%3Dtrue%26f%3D255%26MSPPError%3D-2147217396&view=vs-2019

//伙伴類別(companion class,這裡是companion pointer class)是否就要放在同一個標頭檔中呢?!否則就會出錯

class StrBlobPtr

{

friend class StrBlob;

public:

StrBlobPtr() : curr(0) {}//第1個建構器(也是預設建構器(default constructor)——沒有引數)

StrBlobPtr(StrBlob& a, size_t sz = 0) : wptr(a.data), curr(sz) {}//第2個建構器

std::string& deref() const;

StrBlobPtr& incr(); // 前缀版本(prefix version)

StrBlobPtr& decr(); // 前缀版本(prefix version)

bool isEnd();//用來判斷已到末尾元素

private:

//如果檢查成功,check會回傳一個shared_ptr指向vector

std::shared_ptr<std::vector<std::string>>

check(std::size_t, const std::string&) const;

// wptr儲存一個weak_ptr,利用它來作為一個底層vector可能已摧毁的指示

//(指標;這個智慧指標不是用來管控其所指物的生死,而是用來檢查其所指物件是否還存在)

// store a weak_ptr, which means the underlying vector might be destroyed

std::weak_ptr<std::vector<std::string>> wptr;

std::size_t curr; // curr是用來指示vector中目前元素的位置

// current position within the array——應是英文版筆誤!



};

#endif // !STRBLOB_H





源碼檔(source file)

#include"StrBlob.h"

#include<string>

#include<vector>

#include<memory>

#include <stdexcept>

using namespace std;

StrBlob::StrBlob() : data(make_shared<vector<string>>()) {}

StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {} //用il來作為make_shared引數,就不是空的vector了



void StrBlob::check(size_type i, const string& msg) const

{

if (i >= data->size())

throw out_of_range(msg);

}



string& StrBlob::front()

{

// if the vector is empty, check will throw

check(0, "front on empty StrBlob");

return data->front();

}

string& StrBlob::back()

{

check(0, "back on empty StrBlob");

return data->back();

}



StrBlobPtr StrBlob::begin()

{

return StrBlobPtr(*this);

}



StrBlobPtr StrBlob::end()

{

return StrBlobPtr(*this,this->size());

}

//StrBlobPtr StrBlob::begin() {

// return StrBlobPtr(*this);

//}

//

//StrBlobPtr StrBlob::end()

//{

// auto ret = StrBlobPtr(*this, data->size());

// return ret;

//}

void StrBlob::pop_back()

{

check(0, "pop_back on empty StrBlob");

data->pop_back();

}



// StrBlobPtr的成員函式check會傳回一個shared_ptr的智慧指標,這個指標指向的是元素型別為string的vector物件

std::shared_ptr<std::vector<std::string>>

StrBlobPtr::check(std::size_t i, const std::string& msg) const

{

第90集 12:49:00忘了按暫停,已超過youtube上傳上限,若不能不截斷就請改看臉書直播 https://www.facebook.com/oscarsun72/videos/2515560268555092 或下載原檔來看了。 感恩感恩 南無阿彌陀佛 。可以截斷也,今止於3:50:04

auto ret = wptr.lock();// 用ret區域變數來記下Weak_ptr這個型別StrBlobPtr資料成員wptr,它叫用它的成員函式lock回傳的結果,這個結果是一個shared_ptr型別的指標;用這個表達式(運算式express)來測度wptr所指向的vector物件是否還存在。wptr是由StrBlob資料成員data來初始化的,也就是與shared_ptr型別的data指向的是同一個物件(非強勢性地共享資源),在此這個物件是vector

if (!ret) //如果傳回來的指標為null值,便等於「0」,條件式就會成立(在C++中true=1(只要是非0的都是true),false=0)

throw std::runtime_error("unbound StrBlobPtr");//要用「runtime_error」要記得「#include <stdexcept>」。「"unbound StrBlobPtr"」表示調用這個check的StrBlobPtr型別物件(它是一個指標型別)並沒有指向任何物件(在這裡是vector裡的元素)——沒有和任何物件繫結(bind)在一塊;而StrBlob的資料成員、型別為shared_ptr的data,指向的則是那個vector

留言

熱門文章