C++ explicit&noexcept關鍵字

Gold_stein發表於2024-10-06

C++ explicit&noexcept關鍵字

explicit關鍵字

在 C++ 中,explicit 關鍵字用於避免編譯器在特定情況下進行隱式型別轉換。它主要作用於建構函式和轉換函式,防止不必要或意外的型別轉換髮生,從而提高程式碼的安全性和可讀性。

1. 作用於建構函式

當一個建構函式只接受一個引數時,它通常會被編譯器視為可以進行隱式型別轉換。例如,如果你定義一個建構函式允許透過單個引數初始化物件,編譯器可能會將該引數自動轉換為物件型別。這種隱式轉換有時可能會導致難以除錯的錯誤。

explicit 關鍵字可以禁止這種隱式轉換,使得建構函式只能用於顯式地建立物件。

示例(沒有 explicit):

class MyClass {
public:
    MyClass(int x) { }
};

int main() {
    MyClass obj = 10;  // 隱式呼叫 MyClass(10)
}

在這個例子中,MyClass 的建構函式可以隱式地將 10 轉換為 MyClass 型別的物件。

使用 explicit 禁止隱式轉換:

class MyClass {
public:
    explicit MyClass(int x) { }
};

int main() {
    // MyClass obj = 10;  // 錯誤:不能隱式轉換
    MyClass obj(10);      // 正確:必須顯式呼叫建構函式
}

這裡,透過新增 explicit 關鍵字,編譯器會拒絕隱式轉換,要求必須顯式呼叫建構函式。

2. 作用於轉換函式

轉換函式(即 operator 函式)可以將一個類的物件轉換為其他型別。在某些情況下,自動的型別轉換可能會帶來意想不到的結果,因此 explicit 也可以用於修飾轉換函式,以禁止隱式轉換。

示例(沒有 explicit):

class MyClass {
public:
    operator int() const { return 42; }
};

int main() {
    MyClass obj;
    int x = obj;  // 隱式呼叫轉換函式,將 obj 轉換為 int 型別
}

使用 explicit 禁止隱式轉換:

class MyClass {
public:
    explicit operator int() const { return 42; }
};

int main() {
    MyClass obj;
    // int x = obj;  // 錯誤:不能隱式轉換
    int x = static_cast<int>(obj);  // 正確:必須顯式轉換
}

這裡,explicit 禁止了隱式轉換,需要使用 static_cast 進行顯式轉換。

總結:

  • 建構函式explicit 用於防止透過單引數建構函式的隱式型別轉換,要求顯式建立物件。
  • 轉換函式explicit 用於禁止類物件到其他型別的隱式轉換,要求顯式呼叫轉換函式。

noexcept關鍵字

在 C++ 中,noexcept 是一個關鍵字,用於指定一個函式是否承諾不丟擲異常。它的主要作用是告訴編譯器和程式設計師,這個函式在正常情況下不會丟擲異常,從而可以進行某些最佳化和錯誤處理。

noexcept 的作用:

  1. 宣告函式不會丟擲異常
    當一個函式被標記為 noexcept,它承諾不會丟擲異常。如果該函式在執行時確實丟擲了異常,程式會直接呼叫 std::terminate() 並中止執行,而不會像正常的異常處理流程那樣進行棧展開。

    例如:

    void foo() noexcept {
        // 函式體不會丟擲異常
    }
    
  2. 最佳化編譯器行為
    標記為 noexcept 的函式可以讓編譯器進行更好的最佳化,特別是在那些涉及異常處理的程式碼路徑上。編譯器知道不需要生成棧展開(stack unwinding)程式碼來處理異常,因為 noexcept 承諾不會丟擲異常。

  3. 與異常安全性有關
    在異常安全的程式碼中,noexcept 有助於編寫更具魯棒性(健壯性)的程式碼。某些標準庫的操作(例如容器的某些操作)在處理涉及 noexcept 的函式時,行為可能會有所不同。例如,std::vector 的移動操作要求其元素的移動建構函式是 noexcept 的,否則在擴充套件容量時會回退到複製操作。

  4. 條件性的 noexcept
    noexcept 可以是條件性的,即你可以根據某些條件決定函式是否是 noexcept。這種情況主要用於模板程式碼,確保模板例項化時只有在某些條件下才標記為 noexcept

    例如:

    template <typename T>
    void foo(T&& t) noexcept(noexcept(T())) {
        // 根據 T 的建構函式是否丟擲異常來決定是否是 noexcept
    }
    

使用場景:

  1. 移動建構函式和移動賦值運算子
    如果一個類的移動建構函式或移動賦值運算子被標記為 noexcept,那麼標準庫容器在移動該物件時會更高效。例如:

    class MyClass {
    public:
        MyClass(MyClass&&) noexcept = default;
        MyClass& operator=(MyClass&&) noexcept = default;
    };
    
  2. 解構函式
    通常,解構函式預設是 noexcept。因為在異常傳播時,如果解構函式丟擲異常,C++ 會呼叫 std::terminate()

    例如:

    class Foo {
    public:
        ~Foo() noexcept {
            // 不應丟擲異常
        }
    };
    

noexceptthrow() 的區別:

noexcept 是 C++11 引入的,取代了早期的 throw() 異常規範。throw() 的語義是標記函式不會丟擲任何型別的異常,但它的行為和相容性存在問題,因此被 noexcept 取代。

示例:

void func() noexcept {
    // 保證不丟擲異常
}

void test() {
    try {
        func();
    } catch (...) {
        // 永遠不會捕獲到異常,因為 func 是 noexcept
    }
}

總結來說,noexcept 用來宣告和約束函式不丟擲異常,有助於提高程式的安全性和效能。