C++ 語言與函式庫筆記

最後更新: 2025-12-01 21:05:41 +0800 +0800
原文以英文撰寫。您正在閱讀AI翻譯版本。

這份有限範圍的彙編收集不需要獨立文章的語言與工具鏈規則。每個區段都說明控制該功能的邊界,因為外觀相似的 C++ 寫法可能具有不同的擁有權、查找或生命週期效果。

資源擁有權與選用狀態 #

擁有權是適時釋放資源的責任。使用 RAII 建模:在建構時取得、在解構時釋放,並讓物件的不變量跨越例外控制流程保持成立。當 std::unique_ptr 或 std::vector 等成員已實作資源政策時,優先使用零法則。直接管理原始資源的型別必須定義一致的解構、複製政策、移動政策與失敗政策。

std::make_unique<T>(args...) 表達唯一擁有權,而不暴露獨立的 new;自 C++14 起可用。std::make_shared<T>(args...) 可能合併控制區塊與物件配置。合併配置不一定適用:weak_ptr 可以在物件銷毀後讓該配置保持存活,而自訂刪除或既有原始指標可能需要直接建構。需要直接建構時,在同一個完整運算式中建立智慧指標擁有者。在 C++17 強化函式引數評估順序之前,在較大的呼叫中將配置與擁有權分開,如果另一個引數拋出例外,也可能造成洩漏。

依擁有權與身分選擇選用子物件的表示法:

  • 具有有效空狀態的直接成員沒有間接層,但該狀態必須明確。
  • std::optional<T> 將可能不存在的 T 儲存在包含物件中;emplace 在不另行配置的情況下開始被包含物件的生命週期。
  • std::unique_ptr<T> 表示獨立物件的專屬擁有權,支援不完整或多型型別,但代價是間接層與可為 null 的控制代碼。

optional 與堆積配置都不能讓物件超過擁有者存活。參照、指標或 std::reference_wrapper<T> 是不擁有的控制代碼;被參照物件必須超過所有使用存活。當容器或 std::bind 等以值為導向的介面必須攜帶參照時,std::ref 與 std::cref 很有用。

介面、運算子與物件模型 #

對稱二元運算子通常應是非成員,讓兩個運算元都能考慮轉換。宣告為 friend 會授予範圍狹窄的私有存取;友元不會使它成為成員。

cpp
class Quantity {
public:
    explicit Quantity(int value) : value_{value} {}
    friend Quantity operator+(Quantity left, const Quantity& right) {
        left.value_ += right.value_;
        return left;
    }
private:
    int value_;
};

多型類別具有虛擬函式。標準規定虛擬分派行為,但不規定 vptr、vtable、它們的配置或 ABI。許多實作使用隱藏指標與函式指標表,但多重繼承與編譯器選項會使對該模型的假設失效。應用程式程式碼必須依賴虛擬函式,而不是表示細節。

抽象類別在其類別定義完成後即完整,即使純虛擬函式阻止直接實例化。不完整類別只有宣告,不能用於 sizeof 或直接資料成員。在非虛擬菱形繼承中,衍生物件包含兩個基底子物件,因此未限定的繼承成員可能有歧義。不過,靜態資料成員屬於其宣告類別;它不會為每個基底子物件複製。

cpp
struct A {
    int value;
    static int count;
};
struct B : A {};
struct C : A {};
struct D : B, C {};

int A::count = 0;
D object;
// object.value is ambiguous: object contains two A base subobjects.
D::count = 1; // One static member; no A object is selected.

運算式、參照與解析 #

每個非 void 運算式都是 prvalue、xvalue 或 lvalue。lvalue 與 xvalue 是 glvalue,因為它們識別實體;prvalue 與 xvalue 是 rvalue。具名變數(包括宣告為 T&& 的變數)都是 lvalue 運算式。純量 prvalue 對內建取址運算子沒有可定址的身分。

參照不是物件。具名參照是其被參照型別的 lvalue 運算式。模板替換可以形成參照的參照;折疊會將 T& &、T& && 與 T&& & 化簡為 T&,只有 T&& && 保持為 T&&。這些規則能啟用轉送,而不會建立巢狀參照物件。

const 成員函式會限制隱含物件參數。const 多載可以在 const 與非 const 物件上執行;非 const 多載不能在 const 物件上執行。在 const 成員中,不能透過 *this 修改非 mutable 成員。

當文字可以解析為宣告時,C++ 會將它解析為宣告。這種最令人困惑的解析不會建構物件:

cpp
Widget widget(Factory()); // May declare a function.
Widget object{Factory{}}; // Unambiguously constructs an object.

整數轉換與位移 #

當 int 能表示所有值時,整數提升會將 bool、字元型別、short 與其他排名低於 int 的合資格型別轉換為 int;否則提升為 unsigned int。接著,一般算術轉換會選取共同型別。因此,在 int 能表示每個 unsigned char 值的常見系統上,unsigned char 值的算術會以 int 算術進行。

cpp
unsigned char left = 42;
unsigned char right = 55;
int sum = left + right;

對位移而言,兩個運算元都會進行整數提升。負計數或計數至少等於提升後左運算元寬度時,會產生未定義行為。使用無號型別並驗證計數,以進行可移植的位元操作。C++ 沒有 Java 風格的 >>>;無號值的右移會移入零位元。C++20 將有號右移指定為算術右移,但當表示方式很重要時,位元導向程式碼仍應避免有號運算元。

可呼叫物件、查找與多型 #

每個 lambda 運算式都有獨特的未命名閉包型別;decltype 可以命名特定閉包物件的型別。捕獲會儲存在閉包物件中,因此被捕獲的參照不會延長生命週期。泛型 lambda 具有模板化呼叫運算子,並為所使用的引數型別實例化行為。

未限定函式呼叫會結合普通查找與引數相依查找(ADL)。ADL 會考慮與其引數型別相關聯的命名空間與類別,支援以下自訂模式:

cpp
using std::swap;
swap(left, right); // Standard fallback plus an associated-namespace overload.

限定呼叫會抑制 ADL。不要依賴基本型別的 ADL:int 不會讓呼叫與不相關的命名空間建立關聯。

以函式型別宣告的函式參數會調整為函式型別的指標。函式名稱也會在適用上下文中轉換為函式指標。

c
void callback(void);
void invoke(void f(void));
void invoke_pointer(void (*f)(void));

void use(void) {
    invoke(callback);
    invoke_pointer(&callback);
}

C++ 加入多載集合、模板與成員函式指標,因此函式名稱可能需要目標型別,才能選取一個多載。

臨時多型會從靜態引數型別選取多載。參數多型會依編譯時期要求實例化模板。子型別多型會在執行時期透過基底指標或參照分派虛擬呼叫。型別抹除(例如 std::function、概念/模型包裝器或基底介面)會在邊界以執行時期介面取代具體型別。它可以降低標頭與 ABI 耦合,但可能增加間接層、配置、包裝器儲存與執行時期檢查。

延遲初始化與並行 #

函式區域靜態初始化具執行緒安全性:若多個執行緒同時進入宣告,初始化會在任何執行緒繼續前完成一次,而例外允許稍後重試。若要延遲初始化執行緒共用的成員,使用 std::mutex、std::call_once 或適當的原子協定保護狀態轉換。

cpp
const Settings& settings() {
    static const Settings value = load_settings();
    return value;
}

volatile 不會使存取具原子性,也不會建立 happens-before 關係。它不能取代 mutex、條件變數或 std::atomic,也不保證快取清除或主記憶體流量。只有在平台契約(例如記憶體映射 I/O)要求可觀察的 volatile 存取時才使用它。

翻譯單元相依性 #

當標頭宣告變更需要用戶端重新編譯時,包含標頭會建立編譯時期相依性。在公開介面中暴露具體容器或其迭代器型別,會使用戶端與該表示法耦合。狹窄的抽象介面或 Pimpl 邊界可以保留實作自由,但各自增加介面或間接層成本。

在函式內建構的自動物件會在每次呼叫中建構與銷毀。只為避免建構而將它移到迴圈外,會改變其生命週期,並可能使狀態、資源或同步超出預期範圍而保留。

評論

在 GitHub 上閱讀或評論(發表評論需要登入)