C++自修入門實境秀、C++ Primer 5版研讀秀 98/ ~12.3.1. Design of the Query Program~練習1...
2:20背景轉黑色暗色系
練習12.27 coding
練習12.27
TextQuery和QueryResult類別將會用到的功能並不會超出我們經涵蓋談過的範圍。先別往前看在繼續閱讀前,針對這些類別,先試著依照我們談過的功能寫出你自己的版本的這些類別。
第98集 1:52:00
成功了。沒錄到的部分,請看臉書直播503集2:09:00前後 https://www.facebook.com/oscarsun72/videos/2569013886543063
3:46:24
2:8:00這次12、11章通盤重讀重譯複習一遍真的有價值了!
2:59:47 來試驗當TextQuery死掉時QueryResult還能正常運作,才能彰顯利用智慧指標資源共用的真諦
3:46:40
.cpp
https://github.com/oscarsun72/prog1-C-Primer-5th-Edition-s-Exercises/tree/exercise12_27print_QueryResult_without_TextQuery/prog1
#include<iostream>
#include<memory>
#include <fstream>
#include"TextQuery.h"
#include"QueryResult.h"
using namespace std;
QueryResult textquery(ifstream& ifs,const string & strSearch) {
TextQuery tq(ifs);
QueryResult qr = tq.query(strSearch);
return qr;
}
int main() {
string fName,strSearch;
cout << "請指定要檢索的檔案全名(fullname,含路徑與副檔名)" << endl;
if (cin >> fName);
//必須檢查檔案存不存在
else//若沒有指定檔案的話
{
fName = "V:\\Programming\\C++\\input.txt";
}
cin.clear();//cin前面已經移動它的迭代器(iterator)了到讀取失敗的位置,故要歸零清除,
//否則如果這裡讀取失敗,後面的cin >> strSearch判斷就會永遠都是false(讀取失敗)了
//第89集1:4:00//可參考前面談資料流(stream)的部分
while(true){
ifstream ifs(fName);
cout << "請輸入檢索字串,或輸入「q」離開" << endl;
if (!(cin >> strSearch) || strSearch == "q") break;
QueryResult qr= textquery(ifs,strSearch);
qr.print();
}
}
TextQuery.h
#ifndef TextQuery_H
#define TextQuery_H
#include<vector>
#include<memory>
#include<iostream>
#include<fstream>
#include<sstream>
#include<string>//要用getline函式,要引入這一行
#include<map>
#include<set>
#include "QueryResult.h"
using namespace std;
class TextQuery
{
friend class QueryResult;
typedef pair<map<string, size_t>::const_iterator, map<string, size_t>::const_iterator>
pair_iterator_map;
typedef pair<shared_ptr<vector<string>>, shared_ptr<pair<string, set<size_t>>>> pair_sp_vec_str_sp_pair_str_set;
using iterator_map= map<string, set<size_t>>::iterator;
public:
//TextQuery() ;
TextQuery(ifstream& infile);
~TextQuery();
QueryResult query(const string&);
private:
shared_ptr<vector<string>>spVs;//第89集 2:12:00
//一個map關聯式容器(associative container)因為一個字詞key(string)會有好幾行與之對應,故用
//map,而其「值」為set容器
map<string, set<size_t>>word_lineNum;
};
TextQuery::TextQuery(ifstream& infile)
{
string lStr;
size_t line_Num{ 0 };
vector<string>vs;
spVs = make_shared<vector<string>>(vs);
while (infile && !infile.eof())//第89集2:4:00
{
getline(infile, lStr);
spVs->push_back(lStr);//one line of text in an element
++line_Num;
istringstream isstr(lStr);
string word;
while (isstr >> word)
{
map<string, set<size_t>>::iterator mIter = word_lineNum.find(word);
if (mIter == word_lineNum.end()) {//如果文字行號的map還沒有此文字的話
set<size_t> line_num_st;
line_num_st.insert(line_Num);
word_lineNum.insert(make_pair(word, line_num_st));
}
else//如果文字行號的map已經有此文字的話
mIter->second.insert(line_Num);//若原已有此行號,用insert就不會插入(何況set本來鍵值(就是「值」)就不能重複
}
}
}
TextQuery::~TextQuery()
{
}
QueryResult TextQuery::query(const string& wordForQuery)
{
/*第88集4:18:23//4:31:30回傳的應該是檢索結果,
*(此行註文但作參考)或者試用allocator物件記錄在動態記憶體(dynamic memory),再與QueryResult物件共用此資料*/
//臉書直播第443集、444集。第89集1:18:00
iterator_map wlIter = word_lineNum.find(wordForQuery);
if (wlIter == word_lineNum.end())
{
cout << "沒有找到您要找的字串!" << endl;
set<size_t>st;
return QueryResult(make_shared<pair<string, set<size_t>>>
(make_pair(wordForQuery,st)));//()呼叫運算子(call operator)這裡表示呼叫預設建構器(default constructor)
}
//shared_ptr<pair<string, set<size_t>>> sp = make_shared<pair<string,set<size_t>>>(*wlIter);
//QueryResult qrfound(spVs, sp);
return QueryResult(spVs,make_shared<pair<string,set<size_t>>>(*wlIter));//「()」:呼叫建構器
}
#endif // !TextQuery_H
QueryResult.h
#ifndef QueryResult_H
#define QueryResult_H
#include<vector>
#include<memory>
#include<iostream>
#include<iterator>
#include"TextQuery.h"
using namespace std;
class QueryResult
{
public:
QueryResult(shared_ptr<pair<string,set<size_t>>>sp_key) :pair_str_set(sp_key) { found = false; }
QueryResult(shared_ptr<vector<string>>, shared_ptr<pair<string, set<size_t>>>);
~QueryResult();
void print();
private:
shared_ptr<vector<string>>vs;
shared_ptr<pair<string, set<size_t>>>pair_str_set;
bool found;
};
QueryResult::QueryResult(shared_ptr<vector<string>> sp_vec_str, shared_ptr<pair<string, set<size_t>>> sp_pair_str_set)
{
vs = sp_vec_str;//這樣才是由智慧指標(smart pointer)來管理,而不是
//vs=*sp_vec_str ←原來寫這樣,就是解參考智慧指標後再把解參考的結果容器,指定(即複製一份)
//給「vs」這個資料成員;這樣就不合資源共享(共用資源)的原則,反而成複製一份了。第98集2:40:00
pair_str_set = sp_pair_str_set;
found = true;
}
QueryResult::~QueryResult()
{
}
inline void QueryResult::print()
{
ostream_iterator<string>o(cout);
ostream_iterator<size_t>o_size_t(cout);
*o++ = pair_str_set->first;
*o++ = " occurs ";
if (!found)
*o++ = "0 time";
else//如果有找到
{
*o_size_t++ = pair_str_set->second.size();
*o++ = (pair_str_set->second.size() > 1) ? " times" : " time";
cout << endl;
for (const size_t i : pair_str_set->second)
{
*o++ = "\t(line ";
*o_size_t++ = i;
*o++ = ")";
*o++ = (*vs)[i - 1];
cout << endl;
}
}
cout << endl;
}
#endif // !QueryResult_H
用shared_ptr來管理共享二個容器資源
詳見 https://blog.csdn.net/oscarsun72/article/details/104446129
https://github.com/oscarsun72/prog1-C-Primer-5th-Edition-s-Exercises/tree/exercise12_27with_shared_ptr/prog1
.cpp
#include<iostream>
#include<memory>
#include <fstream>
#include"TextQuery.h"
#include"QueryResult.h"
using namespace std;
pair<shared_ptr<vector<string>>, shared_ptr<map<string, set<size_t>>>> queryData(ifstream& infile)
{
string lStr;
size_t line_Num{ 0 };
vector<string>vs;//主要就是這兩個(vector、map)容器要作為TexQuery與QueryResult資源共享者
map<string, set<size_t>>word_lineNum;
/*用了make_shared函式,就已經動用到了動態記憶體區了:
這個函式會在動態記憶體區中配置並初始化(即建置)一個物件,然後回傳一個shared_ptr指向該物件。和智慧指標一樣,make_shared也是定義在memory標頭檔中。(頁451)
*/
shared_ptr<vector<string>>spVs(make_shared<vector<string>>(vs));//利用智慧指標shared_ptr來達到
shared_ptr<map<string, set<size_t>>>spWord_lineNum(
make_shared<map<string, set<size_t>>>(word_lineNum));//資源共用的目的
while (infile && !infile.eof())//第98集6:46:00
{
getline(infile, lStr);
spVs->push_back(lStr);//one line of text in an element
++line_Num;
istringstream isstr(lStr);
string word;
while (isstr >> word)
{
map<string, set<size_t>>::iterator mIter = spWord_lineNum->find(word);
if (mIter == spWord_lineNum->end()) {//如果文字行號的map還沒有此文字的話
set<size_t> line_num_st;
line_num_st.insert(line_Num);
spWord_lineNum->insert(make_pair(word, line_num_st));
}
else//如果文字行號的map已經有此文字的話
mIter->second.insert(line_Num);//若原已有此行號,用insert就不會插入(何況set本來鍵值(就是「值」)就不能重複
}
}
return make_pair(spVs, spWord_lineNum);
}
int main() {
string fName, strSearch;
cout << "請指定要檢索的檔案全名(fullname,含路徑與副檔名)" << endl;
if (cin >> fName);
//必須檢查檔案存不存在
else//若沒有指定檔案的話
{
fName = "V:\\Programming\\C++\\input.txt";
}
cin.clear();//cin前面已經移動它的迭代器(iterator)了到讀取失敗的位置,故要歸零清除,
//否則如果這裡讀取失敗,後面的cin >> strSearch判斷就會永遠都是false(讀取失敗)了
//第89集1:4:00//可參考前面談資料流(stream)的部分
ifstream ifs(fName);
TextQuery tq(queryData(ifs));
while (true) {
cout << "請輸入檢索字串,或輸入「q」離開" << endl;
if (!(cin >> strSearch) || strSearch == "q") break;
QueryResult qr = tq.query(strSearch);
qr.print();
}
}
TextQuery.h
#ifndef TextQuery_H
#define TextQuery_H
#include<vector>
#include<memory>
#include<iostream>
#include<fstream>
#include<sstream>
#include<string>//要用getline函式,要引入這一行
#include<map>
#include<set>
#include "QueryResult.h"
using namespace std;
class TextQuery
{
friend class QueryResult;
typedef pair<map<string, size_t>::const_iterator, map<string, size_t>::const_iterator>
pair_iterator_map;
typedef pair<shared_ptr<vector<string>>, shared_ptr<pair<string, set<size_t>>>> pair_sp_vec_str_sp_pair_str_set;
using iterator_map = map<string, set<size_t>>::iterator;
public:
//TextQuery() ;
TextQuery(ifstream& infile);
TextQuery(pair<shared_ptr<vector<string>>, shared_ptr<map<string, set<size_t>>>>spPair) :
spVs(spPair.first), word_lineNum(*spPair.second) {};
~TextQuery();
QueryResult query(const string&);
private:
shared_ptr<vector<string>>spVs;//第89集 2:12:00
//一個map關聯式容器(associative container)因為一個字詞key(string)會有好幾行與之對應,故用
//map,而其「值」為set容器
map<string, set<size_t>>word_lineNum;
};
TextQuery::TextQuery(ifstream& infile)
{
string lStr;
size_t line_Num{ 0 };
vector<string>vs;
spVs = make_shared<vector<string>>(vs);
while (infile && !infile.eof())//第89集2:4:00
{
getline(infile, lStr);
spVs->push_back(lStr);//one line of text in an element
++line_Num;
istringstream isstr(lStr);
string word;
while (isstr >> word)
{
map<string, set<size_t>>::iterator mIter = word_lineNum.find(word);
if (mIter == word_lineNum.end()) {//如果文字行號的map還沒有此文字的話
set<size_t> line_num_st;
line_num_st.insert(line_Num);
word_lineNum.insert(make_pair(word, line_num_st));
}
else//如果文字行號的map已經有此文字的話
mIter->second.insert(line_Num);//若原已有此行號,用insert就不會插入(何況set本來鍵值(就是「值」)就不能重複
}
}
}
TextQuery::~TextQuery()
{
}
QueryResult TextQuery::query(const string& wordForQuery)
{
/*第88集4:18:23//4:31:30回傳的應該是檢索結果,
*(此行註文但作參考)或者試用allocator物件記錄在動態記憶體(dynamic memory),再與QueryResult物件共用此資料*/
//臉書直播第443集、444集。第89集1:18:00
iterator_map wlIter = word_lineNum.find(wordForQuery);
if (wlIter == word_lineNum.end())
{
cout << "沒有找到您要找的字串!" << endl;
set<size_t>st;
return QueryResult(make_shared<pair<string, set<size_t>>>
(make_pair(wordForQuery, st)));//()呼叫運算子(call operator)這裡表示呼叫預設建構器(default constructor)
}
//shared_ptr<pair<string, set<size_t>>> sp = make_shared<pair<string,set<size_t>>>(*wlIter);
//QueryResult qrfound(spVs, sp);
return QueryResult(spVs, make_shared<pair<string, set<size_t>>>(*wlIter));//「()」:呼叫建構器
}
#endif // !TextQuery_H
QueryResult.h
#ifndef QueryResult_H
#define QueryResult_H
#include<vector>
#include<memory>
#include<iostream>
#include<iterator>
#include"TextQuery.h"
using namespace std;
class QueryResult
{
public:
QueryResult(shared_ptr<pair<string, set<size_t>>>sp_key) :pair_str_set(sp_key) { found = false; }
QueryResult(shared_ptr<vector<string>>, shared_ptr<pair<string, set<size_t>>>);
~QueryResult();
void print();
private:
shared_ptr<vector<string>>vs;
shared_ptr<pair<string, set<size_t>>>pair_str_set;
bool found;
};
QueryResult::QueryResult(shared_ptr<vector<string>> sp_vec_str, shared_ptr<pair<string, set<size_t>>> sp_pair_str_set)
{
vs = sp_vec_str;//這樣才是由智慧指標(smart pointer)來管理,而不是
//vs=*sp_vec_str ←原來寫這樣,就是解參考智慧指標後再把解參考的結果容器,指定(即複製一份)
//給「vs」這個資料成員;這樣就不合資源共享(共用資源)的原則,反而成複製一份了。第98集2:40:00
pair_str_set = sp_pair_str_set;
found = true;
}
QueryResult::~QueryResult()
{
}
inline void QueryResult::print()
{
ostream_iterator<string>o(cout);
ostream_iterator<size_t>o_size_t(cout);
*o++ = pair_str_set->first;
*o++ = " occurs ";
if (!found)
*o++ = "0 time";
else//如果有找到
{
*o_size_t++ = pair_str_set->second.size();
*o++ = (pair_str_set->second.size() > 1) ? " times" : " time";
cout << endl;
for (const size_t i : pair_str_set->second)
{
*o++ = "\t(line ";
*o_size_t++ = i;
*o++ = ")";
*o++ = (*vs)[i - 1];
cout << endl;
}
}
cout << endl;
}
#endif // !QueryResult_H
Chapter 12. Dynamic Memory
動態記憶體
我們目前寫過的程式,用的物件都具有明確的生命長度(生命週期lifetimes)。全域性的物件會在整個應用程式啟動時先行配置妥當,並在程式結束時才結束其生命。區域性生成的物件則會在程式的執行點進入它們所在的區塊時生成,並在離開該區塊時即結束。由應用程式靜態配置的(static)區域性物件,則會在初次使用到它們前配置妥當,而在整個應用程式結束時才會結束。
除了支援區域性生成的物件和應用程式靜態配置的物件,C++也能讓我們自行動態配置物件(就是有彈性地、自決地、機動地配置物件,能自主地建構與解構該物件)。動態配置的物件具有獨立存在的能力(lifetimes,生命週期),它們的生命長度會一直持續到教它們結束為止。
然而,在運用動態配置物件時,常會碰到的問題卻是在建構這樣的物件時,卻忘了要在適當的時機下解構(刪除)它們,並釋放它們所佔用的記憶體。為了讓動態配置物件得到更妥善地應用,C++的程式庫就定義了兩種型別的智慧指標,來有效管控動態配置物件的生命週期。這兩種型別的智慧指標都能在適當的時機下釋放它們所管控(指向)的動態配置物件佔用的記憶體。第98集8:24:00
頁450
我們到目前為止練習的程式,對於系統記憶體的使用,也只用到靜態的(static)和堆棧式(stack)這兩種記憶體區域。靜態記憶體是用來配置靜態(static)物件的,它包括了:
1.區域性的靜態(static)物件(§6.1.1)
2.類別的靜態(static)資料成員(data members,§7.6)
3.以及在任何函式外定義的變數,
這3種。而堆棧式記憶體則是用來配置非靜態的物件,如定義在函式內的非靜態物件(也就區域性生成的物件,即所謂的自動物件automatic object)即是。
在這兩種記憶體區域(即靜態和堆棧式記憶體)中配置的物件會由編譯器來自動創建並予摧毀。至於堆棧式記憶體配置存放的物件則僅僅在它們被定義的區塊還在執行的時候才有效、才存在;然而若是靜態配置的(static)區域性物件,這樣的物件就會在它們第一次被使用前才配置好,且只會在整個應用程式(program)結束時才被摧毀。
第98集9:44:50
除了上述兩種記憶體區域(靜態與堆棧記憶體)外,作業系統對每個應用程式(program)還都配有一個叫做集區(pool,在這裡應該有點蓄水池的意思。在中文語境中應可翻成「專區」,這個專區是專門給動態配置物件用的!又可名為「動態配置專區」)的記憶體可資使用。這個記憶體區域也叫作自由存放區(free store)或heap(堆積記憶體區;heap英文詞源有負面之義,翻成「堆放、堆置、堆棧」比較恰當。在這裡意思應是「額外、多餘(廚餘)」的意思)。應用程式則會把這個作業系統配置給它的堆積區域(heap)留給動態配置(dynamically allocate)的物件使用。這種物件是程式在執行期間(run time)所動態(機動、自主、自決、自助餐)進行配置的物件,因此程式本身就有責任去好好管控這些動態物件的存廢。我們在撰寫這樣的程式時,就必須在這種物件不再需要的時候明確下達作廢它們的指令,以釋放它們所佔用的記憶體,使資源不致糜費(leak)。
警告:儘管在程式設計的實務中,對於動態記憶體(dynamic memory)的使用幾乎可以說是無法避免的,但在使用動態記憶體時,記憶體資源耗漏(leak)的風險,卻也是令人生畏的。
12.1動態記憶體與智慧指標
C++是利一對運算子來管控動態記憶體的:new運算子能在動態記憶區(free store、pool、heap)中配置一個區塊來安放要建構的物件,且初始化該物件。所以new是建、置(配置與建構-初始化)合一的!在建置(配置與建構-初始化)成功後,會傳回一個指向該物件的普通指標(若未經初始化,則指標指向的將會是一個尚未實際存在的物件,那它不就成了懸置指標(dangling pointer)了);而delete運算子則帶有一個指向動態物件的普通指標的參數,可以藉該參數來摧毀它所指向的動態物件,並釋放其所佔用到的記憶體。
動態記憶體之所以不易管控,是因為不易掌握到報廢動態物件與釋放其所佔用記憶體的正確時機。要不就是忘了釋放被佔用到的記憶體,導致了記憶體資源的浪費——即所謂的記憶體耗漏(memory leak,糜費、洩漏);就是不小心誤將仍在作用中的指標所指的記憶體給釋放了。一旦發生了這樣的失誤,這些指標就會變成懸置指標(dangling pointer),也就是指向了不復存在的物件——即不再有效的記憶體位置。
為了讓動態記憶體的使用更為容易也更加安全(即「便利」:容易=便;利=安全;有「利」則無「害」矣),C++新的程式庫就提供了兩種智慧指標(smart pointer)型別來管控動態物件的存與廢、生成與釋放、建構與解構。智慧指標的表現其實跟一般指標沒什麼兩樣,只不過它具備了一個非常重大且關鍵的特性,那就是:它會在適當的時機自行刪除它所指向的物件;且當它存在的時候,決定不允許它所管控的物件早它消失。(即保證它的物件與它共存亡!)新的程式庫定義的兩種智慧指標,差別只在它們在管理其底層普通指標的方式有所不同(可見智慧指標實質就僅只是一種「委派」性的指標,並不是實質的指標):shared_ptr這樣的智慧指標可容許多個底層的普通指標指向相同的物件,而unique_ptr這種智慧指標,則會「獨佔(owns)」它所指向的物件,不與其他共用。程式庫還定義了一個與這兩種智慧指標相伴的類別,名為weak_ptr,它是一種弱參考型的(weak reference)指標 ,指向由一個shared_ptr管控的物件。這三種智慧指標全都定義在memory這個標頭檔中。
12.1.1 shared_ptr 類別
就像vector,智慧指標是一種模板(templates,§3.3,p96)。因此,創建一個智慧指標的時候,我們必須提供額外的型別資訊,這個型別就是這個指標將要指向的型別。跟定義一個vector一樣,我們會在要定義的智慧指標型別名稱之後,加上角括號來提供這個要指向的型別名稱:
shared_ptr<string> p1;//p1是指向string的shared_ptr
shared_ptr<list<int>> p2;//p2是指向由int組成的list的shared_ptr
頁451
11:6:46而經過預設初始化後的智慧指標,會是一個null指標(==nullptr,§2.3.2,頁54)。在§12.1.3(頁464)我們才會討論到其他初始化智慧指標的方式。
使用智慧指標的方式就如同使用一般的指標。因此,解參考(dereferencing)一個智慧指標就會回傳該智慧指標所指向的物件。也可以在條件句中使用智慧指標,來判斷該指標是否為空指標nullptr :
//如果p1不是空指標nullptr,且它指向的是一個空的string
if (p1 && p1->empty())
*p1 = "hi"; //若是空的string,就將p1解參考,並將新值”hi” 指定給解參考後的結果(string物件)
表12.1(頁452)列出了shared_ptr和unique_ptr共通的運算。專屬於shared_ptr的那些則列於表12.2(頁453)。
make_shared函式
使用動態記憶體最安穩的方式就是呼叫名為make_shared的程式庫函式。這個函式會在動態記憶體區中配置並初始化(即建置)一個物件,然後回傳一個shared_ptr指向該物件。和智慧指標一樣,make_shared也是定義在memory標頭檔中。
當我們呼叫make_shared,我們必須將要建置出來的物件型別指定給make_shard。指定的方式,就如同使用模板類別的方式一樣,必須在make_shared的函式名稱後接著一對角括號,並在其中指定該型別:
// p3指向值為42的 int
shared_ptr<int> p3 = make_shared<int>(42);
// p4指向由10個「9」構成的string,其值為「9999999999」
shared_ptr<string> p4 = make_shared<string>(10, '9' );
// p5指向一個int,其值被值初始化( §3,3.1)為0
shared_ptr<int> p5 = make_shared<int>();
就像循序容器的emplace成員函式一樣(§9.3.1),make_shared也會利用提供給它的引數型別來建置出一個該型別的物件。比如說,對make_shared<string>()的呼叫,就必須傳入一個能與string類別定義的建構器匹配的引數。而對make_shared<int>()的呼叫則可以傳入能夠用來初始化一個int的任何值,來作為make_shared<int>()的引數。如果我們沒有提供引數給make_shared,那麼make_shared所創建出來的物件就會是一個角括弧內所指定的型別經過值初始化後的(§3.3.1)物件。
一般說來,我們在定義make_shared回傳值存放的物件的時候,都會使用auto(§2.5.2)來簡化繁瑣的具名全稱:
// p6指向一個動態配置的、空的vector<string>
auto p6 = make_shared<vector<string>>();
這就是「shared_ptr<vector<string>> p6 = make_shared<vector<string>>();」的簡化。
留言