C++ Primer 筆記-第18章 用于大型程序的工具

18.1 異常處理
異常處理(exception handing)?機制允許程序中獨立開發(fā)的部分能夠在運行時就出現(xiàn)的問題進行通信并做出相應(yīng)的處理。
18.1.1 拋出異常
通過拋出(throwing)?一條表達式來引發(fā)(raised)一個異常。
被拋出表達式的類型以及當前的調(diào)用鏈共同決定了哪段處理代碼(handling)?將被用于處理該異常。
當執(zhí)行一個
throw
時,跟在throw
后面的語句將不再執(zhí)行,然后進行棧展開(stack unwinding)。棧展開過程沿著嵌套函數(shù)的調(diào)用鏈不斷查找,直到找到了異常匹配的catch
子句為止;或者也可能一直沒找到匹配的catch
,程序?qū)⒄{(diào)用標準庫函數(shù)terminate
終止程序的執(zhí)行類對象分配的資源將由類的析構(gòu)函數(shù)負責釋放。因此,如果我們使用類來控制資源的分配,就能確保無論函數(shù)正常結(jié)束還是遭遇異常,資源都能被正確地釋放。
出于棧展開可能使用析構(gòu)函數(shù)的考慮,析構(gòu)函數(shù)不應(yīng)該拋出不能被它自身處理的異常。如果析構(gòu)函數(shù)需要執(zhí)行某個可能拋出異常的操作,則該操作應(yīng)該被放置在一個
try
語句塊當中,并且在析構(gòu)函數(shù)內(nèi)部得到處理。異常對象(exception object)是一種特殊的對象,編譯器使用異常拋出表達式來對異常對象進行拷貝初始化。因此,
throw
語句中的表達式必須擁有完全類型。當我們拋出表達式時,該表達式的靜態(tài)編譯時類型決定了異常對象的類型。拋出指針要求在任何對應(yīng)的處理代碼存在的地方,指針所指的對象都必須存在。
18.1.2 捕獲異常
catch子句(catch clause)?中的?異常聲明(exception declaration)?看起來像是只包含一個形參的函數(shù)形參列表。像在形參列表中一樣,如果?
catch
?無須訪問拋出的表達式的話,則我們可以忽略捕獲形參的名字。通常情況下,如果
catch
接受的異常與某個繼承體系有關(guān),則最好將該catch
的參數(shù)定義成引用類型。如果在多個
catch
語句的類型之間存在著繼承關(guān)系,則我們應(yīng)該把繼承鏈最底端的類(most derived type)放在前面,而將繼承鏈最頂端的類(least derived type)放在后面。一條
catch
語句通過重新拋出(rethrowing)的操作將異常傳遞給另一個catch
語句。這里的重新拋出仍然是一條throw
語句,只不過不包含任何表達式。為了一次性捕獲所有異常,我們使用省略號作為異常聲明,這樣的處理代碼稱為捕獲所有異常(catch-all)?的處理代碼,形如?
catch(...)
。如果
catch(...)
與其他幾個catch
語句一起出現(xiàn),則catch(...)
必須在最后的位置。出現(xiàn)在捕獲所有異常語句后面的catch
語句將永遠不被匹配。
18.1.3 函數(shù)try
語句塊與構(gòu)造函數(shù)
要想處理構(gòu)造函數(shù)初始值拋出的異常,我們必須將構(gòu)造函數(shù)寫成函數(shù)try語句塊(也稱為函數(shù)測試塊,function try block)的形式。
函數(shù)
try
語句塊使得一組catch
語句既能處理構(gòu)造函數(shù)體(或析構(gòu)函數(shù)),也能處理構(gòu)造函數(shù)的初始化過程(或析構(gòu)函數(shù)的析構(gòu)過程)。

tempalte <typename T>
Blob<T>::Blob(std::initializer_list<T> il) try :
? ?data(std::make_shared<std::vector<T>>(il)) {
? ? ? ?// 函數(shù)載體
} catch(const std::bad_alloc& e) { handle_out_of_memory(e); }

和其他函數(shù)調(diào)用一樣,如果在參數(shù)初始化的過程中發(fā)生了異常,則該異常屬于調(diào)用表達式的一部分,并將在調(diào)用者所在的上下文中處理。
處理構(gòu)造函數(shù)初始值異常的唯一方法是將構(gòu)造函數(shù)寫成函數(shù)
try
語句塊。
18.1.4?noexcept
異常說明
c++11新標準中,可以通過提供noexcept說明(noexcept specification)?指定某個函數(shù)不會拋出異常。

void recoup(int) noexcept; ?// 不會拋出異常
void alloc(int); ? ? ? ? ? ?// 可能會拋出異常
void recoup(int) noexcept(true); ? ?// 不會拋出異常
void alloc(int) noexcept(false); ? ?// 可能拋出異常

noexcept
說明要么出現(xiàn)在該函數(shù)的所有聲明語句和定義語句中,要么一次也不出現(xiàn)。編譯器不會在編譯時檢查
noexcept
說明。如果一個函數(shù)在說明了noexcept
的同時又含有throw
語句或者調(diào)用了可能拋出異常的其他函數(shù),編譯器將順利編譯通過,并不會因為這種違反異常說明的情況而報錯。

void f() noexcept(noexcept(g())); ? // f和g的異常說明一致
// noexcept有兩層含義:
// 當跟在函數(shù)參數(shù)列表后面時它是異常說明符;
// 而當作為noexcept異常說明的bool實參出現(xiàn)時,它是一個運算符。

函數(shù)指針及該指針所指的函數(shù)必須具有一致的異常說明。
如果一個虛函數(shù)承諾了它不會拋出異常,則后續(xù)派生出來的虛函數(shù)也必須做出同樣的承諾;與之相反,如果基類的虛函數(shù)允許拋出異常,則派生類的對應(yīng)函數(shù)既可以允許拋出異常,也可以不允許拋出異常。

18.2 命名空間
多個庫將名字放置在全局命名空間中將引發(fā)命名空間污染(namespace pollution)。
命名空間(namespace)為防止名字沖突提供了更加可控的機制。
18.2.1 命名空間定義
和其他名字一樣,命名空間的名字也必須在定義它的作用域內(nèi)保持唯一。命名空間既可以定義在全局作用域內(nèi),也可以定義在其他命名空間中,但是不能定義在函數(shù)或類的內(nèi)部。
命名空間作用域后無須分號。
每個命名空間都是一個作用域。
定義多個類型不相關(guān)的命名空間應(yīng)該使用單獨的文件分別表示每個類型(或關(guān)聯(lián)類型構(gòu)成的集合)。
我們不把
#include
放在命名空間內(nèi)部。如果我們這么做了,隱含的意思是把頭文件中所有的名字定義成該命名空間的成員。模板特例化必須定義在原始模板所屬的命名空間中。和其他命名空間名字類似,只要我們在命名空間中聲明了特例化,就能在命名空間外部定義它了。
全局作用域中定義的名字(既在所有類,函數(shù)及命名空間之外定義的名字)也就是定義在全局命名空間(global namespace)?中。全局作用域中定義的名字被隱式地添加到全局命名空間中。
作用域運算符同樣可以用于全局作用域的成員,因為全局作用域是隱式的,所以它沒有名字:
::member_name
表示全局命名空間中的一個成員。嵌套的命名空間是指定義在其他命名空間中的命名空間。
c++11新標準引入了一種新的嵌套命名空間,稱為內(nèi)聯(lián)命名空間(inline namespace)。
內(nèi)聯(lián)命名空間中的名字可以被外層命名空間直接使用。也就是說,我們無須在內(nèi)聯(lián)命名空間的名字前添加表示該命名空間的前綴,通過外層命名空間的名字可以直接訪問它。
未命名的命名空間(unnamed namespace)?是指關(guān)鍵字namespace后緊跟花括號括起來的一些列聲明語句。未命名的命名空間中定義的變量擁有靜態(tài)的生命周期:它們在第一次使用前創(chuàng)建,并且直到程序結(jié)束才銷毀。
和其他命名空間不同,未命名的命名空間僅在特定的文件內(nèi)部有效,其作用范圍不會橫跨多個不同的文件。
定義在未命名的命名空間中的名字可以直接使用,畢竟我們找不到什么命名空間的名字來限定它們;同樣的,我們也不能對未命名的命名空間的成員使用作用域運算符。
一個未命名的命名空間也能嵌套在其他命名空間當中。此時,未命名的命名空間中的成員可以通過外層命名空間的名字來訪問。
在文件中進行靜態(tài)聲明的做法已經(jīng)被c++標準取消了,現(xiàn)在的做法是使用未命名的命名空間。
18.2.2 使用命名空間成員
命名空間的別名(namespace alias)?使得我們可以為命名空間的名字設(shè)定一個短得多的同義詞。一個命名空間可以有好幾個同義詞或別名,所有別名都與命名空間原來的名字等價。
一條?using聲明(using-declaration)?語句一次只引入命名空間的一個成員:
using std::cout
一條
using
聲明語句可以出現(xiàn)在全局作用域、局部作用域、命名空間作用域以及類的作用域中。using指示(using directive)?和?
using
?聲明類似的地方是,我們可以使用命名空間名字的簡寫形式:using std
;和using
聲明不同的地方是,我們無法控制哪些名字是可見的,因為所有名字都是可見的。using
指示使得某個特定的命名空間中所有的名字都可見,這樣我們就無須再為它們添加任何前綴限定符了。如果我們提供了一個對
std
等命名空間的using
指示而未做任何特殊控制的話,將重新引入由于使用了多個庫而造成的名字沖突問題。using
指示一般被看作是出現(xiàn)再最近的外層作用域中。頭文件如果再其頂層作用域中含有
using
指示或using
聲明,則會將名字注入到所有包含了該頭文件的文件中。頭文件最多只能在它的函數(shù)或命名空間內(nèi)使用using
指示或using
聲明。在命名空間本身的實現(xiàn)文件中就可以使用
using
指示。
18.2.3 類、命名空間與作用域
可以從函數(shù)的限定名推斷出查找名字時檢查作用域的次序,限定名以相反次序指出被查找的作用域。
當我們給函數(shù)傳遞一個類類型的對象時,除了在常規(guī)的作用域查找外,還會查找實參所屬的命名空間。
18.2.4 重載與命名空間
using
聲明語句聲明的是一個名字,而非一個特定的函數(shù)。當我們?yōu)楹瘮?shù)書寫
using
聲明時,該函數(shù)的所有版本都被引入到當前作用域中。using
指示將命名空間的成員提升到外層作用域中,如果命名空間的某個函數(shù)與該命名空間所屬作用域的函數(shù)同名,則命名空間的函數(shù)將被添加到重載集合中。如果存在多個
using
指示,則來自每個命名空間的名字都會成為候選函數(shù)集的一部分。

18.3 多重繼承與虛繼承
多重繼承(multiple inheritance)?是指從多個直接基類中產(chǎn)生派生類的能力。多重繼承的派生類繼承了所有父類的屬性。
18.3.1 多重繼承
基類的構(gòu)造順序與派生類列表中基類的出現(xiàn)順序保持一致,而與派生類構(gòu)造函數(shù)初始值列表中基類的順序無關(guān)。
在c++11新標準中,允許派生類從它的一個或幾個基類中繼承構(gòu)造函數(shù)。但是如果從多個基類中繼承了相同的構(gòu)造函數(shù)(既形參列表完全相同)則程序?qū)a(chǎn)生錯誤。
如果一個類從它的多個基類中繼承了相同的構(gòu)造函數(shù),則這個類必須為該構(gòu)造函數(shù)定義它自己的版本。
析構(gòu)函數(shù)的調(diào)用順序正好與構(gòu)造函數(shù)相反。
18.3.2 類型轉(zhuǎn)換與多個基類
與只有一個基類的繼承一樣,對象、指針和引用的靜態(tài)類型決定了我們能夠使用哪些成員。
18.3.3 多重繼承下的類作用域
在多重繼承的情況下,相同的查找過程在所有直接基類中同時進行。如果名字在多個基類中都被找到,則對該名字的使用將具有二義性。
當一個類擁有多個基類時,有可能出現(xiàn)派生類從兩個或更多基類中繼承了同名成員的情況。此時,不加前綴限定符直接使用該名字將發(fā)生二義性。
18.3.4 虛繼承
虛繼承(virtual inheritance)?的目的是令某個類做出聲明,承諾愿意共享它的基類。其中,共享的基類子對象稱為虛基類(virtual base class)?。在這種機制下,不論虛基類在繼承體系中出現(xiàn)了多少次,在派生類中都只包含唯一一個共享的虛基類子對象。
使用虛繼承的類層次是由一個人或一個項目組一次性設(shè)計完成的。對于一個獨立開發(fā)的類來說,很少需要基類中的某一個是虛基類,況且新基類的開發(fā)者也無法改變已存在的類體系。
虛派生只影響從指定了虛基類的派生類中進一步派生出的類,它不會影響派生類本身。
不論基類是不是虛基類,派生類對象都能被可訪問基類的指針或引用操作。

// 關(guān)鍵字public 和 virtual 的順序隨意
class Raccoon : public virtual ZooAnimal { ... }
class Bear : virtual public ZooAnimal { ... }

18.3.5 構(gòu)造函數(shù)與虛繼承
在虛派生中,虛基類是由最低層的派生類初始化的。
含有虛基類的對象的構(gòu)造順序與一般的順序稍有區(qū)別:首先使用提供給最低層派生類構(gòu)造函數(shù)的初始值初始化該對象的虛基類子部分,接下來按照直接基類在派生類列表中出現(xiàn)的次序依次對其進行初始化。
虛基類總是先于非虛基類構(gòu)造,與它們在繼承體系中的次序和位置無關(guān)。
一個類可以有多個虛基類。此時,這些虛的子對象按照它們在派生列表中出現(xiàn)的順序從左向右依次構(gòu)造。

