1. 程式人生 > 實用技巧 >【C++】《C++ Primer 》第十八章

【C++】《C++ Primer 》第十八章

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

大規模應用程式的特殊要求包括:

  • 在獨立開發的子系統之間協同處理錯誤的能力。
  • 使用各種庫進行協同開發的能力。
  • 對比較複雜的應用概念建模的能力。

一、異常處理

異常處理(exception handling) 機制允許程式中獨立開發的部分能夠在執行時就出現的問題進行通訊並作出相應的處理。

1. 丟擲異常

  • 一個異常如果沒有被捕獲,則它將終止當前的程式。
  • 在棧展開的過程中,執行類型別的區域性物件的解構函式。因為這些解構函式是自動執行的,所以它們不應該丟擲異常。一旦在棧展開的過程中解構函式丟擲了異常,並且解構函式自身沒能捕獲到該異常,則程式將被終止。
  • 當丟擲一條表示式時,該表示式的靜態編譯時型別決定了異常物件的型別。丟擲指標要求在任何對應的處理程式碼存在的地方,指標所指的物件都必須存在。

2. 捕獲異常

  • 通常情況下,如果catch接受的異常與某個繼承體系有關,則最好將該catch的闡述定義為引用型別。
  • 如果在多個catch語句的型別之間存在著繼承關係,則我們應該把繼承鏈最低端的類放在前面,而將繼承鏈最頂端的類放在後面。
  • 如果catch(...)與其他幾個catch語句一起出現,則catch(...)必須在最後的位置。出現在捕獲所有異常語句後面的catch語句將永遠不會被匹配。

3. 函式try語句塊與建構函式

  • 處理建構函式初始值異常的唯一方法是將建構函式寫成函式try語句塊。

4. noexcept異常說明

  • C++11新標準中,通過提供noexcept說明符指定某個函式不會丟擲異常。
void recoup(int) noexcept;  // 不會丟擲異常
void alloc(int);    // 可能丟擲異常

  • 可以在函式指標的宣告和定義中指定noexcept;在typedef或類型別名中則不能出現noexcept;在成員函式中,noexcept說明符需要跟在const及其引用限定符之後,finaloverride或虛擬函式的=0之前。
  • noexcept可以用在兩種情況下:確定函式不會丟擲異常根本不知道該如何處理異常
  • 通常情況下,編譯器不能也不必在編譯時驗證異常說明。
  • noexcept有兩層含義:當跟在函式引數列表後面時它是異常說明符;而當作為noexcept異常說明的bool
    實參出現時,它是一個運算子。

5. 異常類層次

  • 標準exception層次:

  • 書店應用程式的異常類
class out_of_staock: public std::runtime_error {
public:
    explicit out_of_stock(const std::string &s): std::runtime_error(s) {}
};

class isbn_mismatch: public std::logic_error {
    public:
    explicit isbn_mismatch(const std::string &s): std::logic_error(s) {}
    isbn_mismatch(const std::string &s, const std::string &lhs, const std::string &rhs): std::logic_error(s), left(lhs), right(rhs) {}
    const std::string left. right;
};

// 使用自定義異常類
Sales_data &Sales_data::operator += (const Sales_data& rhs) {
    if(isbn() != rhs.isbn())
        throw isbn_mismatch("wrong isbn", isbn(), rhs.isbn());
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}

Sales_data item1, item2, sum;
while (cin >> item1 >> item2) {
    try {
        sum = item1 + item2;
    } catch (const isbn_mismatch &e) {
        cerr << e.what() << ": left isbn(" << e.left << ") right isbn (" << e.right << ")" << endl;
    }
}

二、名稱空間

多個庫將名字放置在全域性名稱空間中將引發名稱空間汙染名稱空間(namespace) 為防止名字衝突提供了更加可控的機制。

1. 名稱空間定義

  • 一個名稱空間的定義包括兩個部分:首先是關鍵字namespace,隨後是名稱空間的名字。在名稱空間名字後面是一系列由花括號括起來的宣告和定義。只要能出現在全域性作用域中的宣告就能置於名稱空間內,主要包括:類、變數(及其初始化操作)、函式(及其定義)、模板和其他名稱空間。
namespace cplusplus_primer {
    class Sales_data {};
    Sales_data operator+(const Sales_data&, const Sales_data&);
    class Query {};
    class Query_base {};
}

// 使用
cplusplus_primer::Query q = cplusplus_primer::Query("hello");
  • 定義多個型別不相關的名稱空間應該使用單獨的檔案分別表示每個型別(或關聯型別構造的集合)。
  • 未命名的名稱空間是指關鍵字namespace後緊跟花括號括起來的一系列宣告語句。它定義的變數擁有靜態生命週期;他們在第一次使用前建立,並且直到程式結束時才銷燬。和其他名稱空間不同,未命名的名稱空間僅在特定的檔案內部有效,其作用範圍不會橫跨多個不同的檔案
int i;  // i的全域性宣告
namespace local {
    namespace {
        int i;
    }
}

local::i = 42;
  • 未命名的名稱空間取代檔案的靜態宣告。

2. 使用名稱空間成員

  • 一個名稱空間可以有好幾個同義詞或別名,所有別名都與名稱空間原來的名字等價。
namespace cplusplus_primer {};

namespace primer = cplusplus_primer;
  • 避免using指示

3. 類、名稱空間與作用域

  • 對名稱空間內部名字的查詢遵循常規的查詢規則:即由內向外依次查詢每個外層作用域。外層作用域也可能是一個或多個巢狀的名稱空間,直到最外層的全域性名稱空間查詢過程終止。只有位於開放的塊中且在使用點之前的名字才被考慮。
  • 可以從函式的限定名與推斷出查詢名字時檢查作用域的次序,限定名以相反次序指出被查詢的作用域。

4. 過載與名稱空間

using宣告語句宣告的是一個名字,而非特定的函式,也就是包括該函式的所有版本,都被引入到當前作用域中。

三、多重繼承與虛繼承

1. 多重繼承

2. 型別轉換與多個基類

3. 多重繼承下的類作用域

  • 當一個類擁有多個基類時,有可能出現派生類從兩個或更多基類中繼承了同名成員的情況。此時,不加字首限定符直接使用該名字將引發二義性。

4. 虛繼承

  • 虛繼承的目的是令某個類做出宣告,承諾願意共享它的基類。其中,共享的基類子物件成為虛基類。在這種機制下,不論虛基類在繼承體系中出現了多少次,在派生類中都只包含唯一一個共享的虛基類子物件。
  • 虛派生隻影響從指定了虛基類的派生類中進一步派生出的類,它不會影響派生類本身。

5. 建構函式與虛繼承

  • h含有虛基類的物件的構造順序與一般的順序稍有區別:首先使用提供給最底層派生類建構函式的初始值初始化該物件的虛基類子部分,接下來按照直接基類在派生列表中出現的次序對其進行初始化。
  • 虛基類總是先於非虛基類構造,與它們在繼承體系中的次序和位置無關。