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
的作用:
-
宣告函式不會丟擲異常:
當一個函式被標記為noexcept
,它承諾不會丟擲異常。如果該函式在執行時確實丟擲了異常,程式會直接呼叫std::terminate()
並中止執行,而不會像正常的異常處理流程那樣進行棧展開。例如:
void foo() noexcept { // 函式體不會丟擲異常 }
-
最佳化編譯器行為:
標記為noexcept
的函式可以讓編譯器進行更好的最佳化,特別是在那些涉及異常處理的程式碼路徑上。編譯器知道不需要生成棧展開(stack unwinding)程式碼來處理異常,因為noexcept
承諾不會丟擲異常。 -
與異常安全性有關:
在異常安全的程式碼中,noexcept
有助於編寫更具魯棒性(健壯性)的程式碼。某些標準庫的操作(例如容器的某些操作)在處理涉及noexcept
的函式時,行為可能會有所不同。例如,std::vector
的移動操作要求其元素的移動建構函式是noexcept
的,否則在擴充套件容量時會回退到複製操作。 -
條件性的
noexcept
:
noexcept
可以是條件性的,即你可以根據某些條件決定函式是否是noexcept
。這種情況主要用於模板程式碼,確保模板例項化時只有在某些條件下才標記為noexcept
。例如:
template <typename T> void foo(T&& t) noexcept(noexcept(T())) { // 根據 T 的建構函式是否丟擲異常來決定是否是 noexcept }
使用場景:
-
移動建構函式和移動賦值運算子:
如果一個類的移動建構函式或移動賦值運算子被標記為noexcept
,那麼標準庫容器在移動該物件時會更高效。例如:class MyClass { public: MyClass(MyClass&&) noexcept = default; MyClass& operator=(MyClass&&) noexcept = default; };
-
解構函式:
通常,解構函式預設是noexcept
。因為在異常傳播時,如果解構函式丟擲異常,C++ 會呼叫std::terminate()
。例如:
class Foo { public: ~Foo() noexcept { // 不應丟擲異常 } };
noexcept
和 throw()
的區別:
noexcept
是 C++11 引入的,取代了早期的 throw()
異常規範。throw()
的語義是標記函式不會丟擲任何型別的異常,但它的行為和相容性存在問題,因此被 noexcept
取代。
示例:
void func() noexcept {
// 保證不丟擲異常
}
void test() {
try {
func();
} catch (...) {
// 永遠不會捕獲到異常,因為 func 是 noexcept
}
}
總結來說,noexcept
用來宣告和約束函式不丟擲異常,有助於提高程式的安全性和效能。