1. 程式人生 > >c++ primer 第十八章用於大型程式的工具

c++ primer 第十八章用於大型程式的工具

c++ primer 第十八章用於大型程式的工具

18.1 異常處理

異常處理機制允許程式中獨立開發的部分能夠在執行時對出現的問題進行通訊並進行相應處理。使得問題的檢測和解決過程分離。

18.1.1 丟擲異常

丟擲一條表示式來引發一個異常。匹配的處理程式碼是呼叫鏈中與丟擲物件型別匹配的最近的處理程式碼。

throw後面的程式碼不再執行,控制權轉移到對應的catch程式碼。因此:1.呼叫鏈中的函式可能提前退出。2.執行異常處理程式碼時呼叫鏈中建立的物件會被銷燬。

try catch匹配時的順序規則,稱為棧展開。找到匹配的catch語句為止,沒找到則程式終止。

在棧展開的過程中退出了塊,編譯器負責確保塊中建立的物件能被正確銷燬。

解構函式需要釋放塊中分配的資源,因此不應該提前結束,因此不能丟擲不能被自身處理的異常。

異常物件是一種特殊物件,丟擲表示式對其進行拷貝初始化,因此必須擁有完全型別。

丟擲表示式時,表示式的靜態編譯時型別決定了其型別。基類指標指向派生類的丟擲物件會被切掉一部分。

18.1.2 捕獲異常

catch子句的異常宣告像是隻包含一個形參的函式形參列表。宣告型別決定捕捉的異常型別。可以是左值引用。

靜態型別決定異常物件的操作,非引用型別物件會被切到與靜態宣告型別相符。

catch匹配順序是從前往後依次匹配,允許非常量向常量的轉換,派生類到基類的轉換以及陣列和函式到指標的轉換。

catch語句中進行一定的處理之後可以重新丟擲異常給更上層語句,此時丟擲語句是throw;。丟擲的是原來的異常物件。

catch(…)可以捕獲所有型別的異常,因此一般放在最後一個或者單獨出現。

18.1.3 函式try語句塊與建構函式

處理建構函式中丟擲的異常需要將try catch語句和建構函式寫在一起。

18.1.4 noexcept異常說明

使用noexcept符號可以設定函式不丟擲異常。加在const和引用說明符後面,在final、override或虛擬函式=0之前。

標明noexcept卻丟擲異常的函式會導致程式直接terminate。

noexcept也可以接受一個bool實參,true表示不丟擲異常。

noexcept也可以當做表示式,引數是函式。當函式呼叫的方法都不丟擲異常為真。

函式指標與其所指函式有一致的異常說明。

虛擬函式不丟擲異常那麼派生的函式也不丟擲。如果虛擬函式允許那麼派生類對應函式都可以。

編譯器合成拷貝控制成員時也生成一個異常說明,根據所有成員和基類操作而定。

18.1.5 異常類層次

exception類定義了拷貝建構函式,拷貝構造運算子,解構函式和一個名為what的虛擬函式成員。

exception派生出bad_cast、bad_alloc、runtime_error和logic_error四種類型的異常。

可以繼承基本型別的異常使用我們自己的異常型別。

18.2 名稱空間

名稱空間防止名字衝突分割了全域性名稱空間,每個名稱空間是一個作用域。

18.2.1 名稱空間定義

名稱空間namespace加名字和{}作為一個作用域,裡面可以包括所有可以放在全域性作用域中的宣告。

名稱空間可以定義在全域性作用域中,也可以在其它名稱空間中。但不能在函式或類的內部。

每個名稱空間都是一個作用域,不同名稱空間可以擁有相同名字的成員。

名稱空間可以是不連續的。因此可以幫助介面和實現的分離。

一般不把#include放在名稱空間內部。因為會把標頭檔案中的所有名字作為該名稱空間的成員。

可以在名稱空間的外部定義該空間的成員。但是隻能在該名稱空間的外層空間中定義。

全域性名稱空間使用::表示來呼叫。

名稱空間可以巢狀,使用時需要全部寫出來。

內聯名稱空間在namespace前加inline。inline名稱空間中的名字可以被外層名稱空間直接使用。

未命名的名稱空間namespace{}。其中的變數擁有靜態生命週期。在新標準中替代全域性static。

未命名的名稱空間可以不連續但是不能跨越檔案。跨檔案的未命名的名稱空間是不同的作用域。

18.2.2 使用名稱空間成員

命名空間別名 namespace primer = cplusplus_primer;等,也可以等於一個巢狀的名稱空間。

using宣告。宣告名稱空間中的一個成員。從宣告的地方開始,到所在作用域結束為止。在此期間外層作用域名字被隱藏。在類作用域中using宣告只能作用域基類成員。

using指示 using namespace std; 直接將名稱空間中所有名字都可見。但不能用在類作用域中。

using指示作用域較為複雜。一般using指示被看做是出現在最近的外層作用域中。使用using指示時允許出現名字衝突,但呼叫時若未加限定則出現二義性錯誤。

標頭檔案頂層作用域包含using指示時,會將該名稱空間注入到所有包含該標頭檔案的檔案內。因此一般在標頭檔案中最多隻能在函式或名稱空間中使用using指示或using宣告。

18.2.3 類、名稱空間和作用域

名稱空間的名字查詢與普通作用域相同。名稱空間中的類成員現在成員中查詢,然後在類中(基類中)查詢,之後到外層空間。

名稱空間的名字隱藏規則有個例外:當我們給函式傳遞一個類型別的物件時,除了常規作用域查詢,還會查詢實參類所屬的名稱空間。

使用std::move和std::forward需要指明名稱空間避免衝突。

友元宣告會被隱式當做是其最近的外層名稱空間的成員。與隱藏規則相互作用。

18.2.4 過載與名稱空間

名稱空間會對函式匹配過程有兩方面影響,一個是會使得某些函式加入候選。

實參是類型別時會影響函式匹配的查詢過程。

using宣告的是一個名字,宣告一個函式時會引入所有版本。

using宣告引入的函式會過載當前所屬作用域的同名函式。using宣告在區域性作用域時會隱藏外部名字。如果所屬作用域有完全相同宣告的函式會引起錯誤。

使用using指示將空間中的名字引入外層作用域中,若同名則加入過載。如果完全相同也不會出錯。但是使用時需要指明版本。

多個using指示的同名函式都會加入過載。

18.3 多重繼承與虛繼承

多重繼承指的是從多個直接基類中產生派生類的能力。

18.3.1 多重繼承

派生列表中可以有多個基類,每個類都有一個訪問說明符。

多重繼承中派生類的物件包含每個基類的子物件。

構造一個派生類的物件將同時構造並初始化它的所有基類子物件,構造順序與派生列表的出現順序一致。

派生類可以從一個或多個基類中繼承建構函式。如果從多個基類中繼承了相同的建構函式(形參列表完全相同)則程式產生錯誤。因此需要定義新的建構函式。

解構函式的呼叫順序與建構函式相反。

多重繼承的派生類如果定義了自己的拷貝/賦值建構函式和賦值運算子,必須在完整的物件上進行。

18.3.2 型別轉換與多個基類

多重繼承的派生類成員可以轉換到任意一個基類的指標或引用。編譯器認為它們一樣好。

一個指標或引用的靜態型別決定了它能夠使用的成員。

18.3.3 多重繼承下的類作用域

多重繼承下,相同的查詢過程在所有基類中同時進行。若名字在多個基類中被找到,引發二義性。

繼承不同基類中的同名成員是合法的,但是使用時需要指定。

因為編譯器先查詢名字後進行型別檢查,因此在派生類繼承的兩個引數形參列表不同也可能發生錯誤等等。

18.3.4 虛繼承

實際上派生類可以多次繼承同一個類,不過是不同的直接基類。

如果某個類在派生過程中出現了多次,派生類中將包含該類的多個子物件。

虛繼承可以使得繼承體系中的某個基類進行共享,派生類中只含一個該類的物件。

在派生類列表對應類前加關鍵字virtual宣告虛基類。

虛基類的成員可以直接呼叫,不存在二義性。

18.3.5 建構函式與虛繼承

虛基類需要由最低層的派生類初始化。

含有虛基類的物件的構造順序為:首先使用提供給最低層派生類建構函式的初始值初始化該物件虛基類子部分,接著按照直接基類在派生列表中出現的次序進行初始化。

解構函式虛構造順序相反。