隱性轉換 (Implicit Conversions)
不要定義隱性轉換。 轉換運算子和單一引數的建構函式應標記為
explicit
。
定義
隱性轉換允許將一種型別 (稱為「來源型別」) 的物件用於原本預期另一種不同的型別 (稱為「目的型別」) 的地方,例如將一個 int
的引數傳給預期接受 double
引數的函式。
除了語言本身定義的隱性轉換之外,使用者也可以藉由在來源型別或目的型別的類別中加入適當的成員來自行定義。 若要在來源型別中定義隱性轉換,可透過定義一個名稱為目的型別的型別轉換運算子來完成(例如:operator bool()
)。 若要在目的型別中定義隱性轉換,則是透過定義一個僅接受來源型別作為唯一引數的建構函式(或唯一沒有預設值的引數)來完成。
可以在建構函式或轉換運算子前加上 explicit
關鍵字,以確保只有在使用處明確指定目的型別(例如透過強制轉型)時才能使用。 這個限制除了應用於隱性轉換外,也會應用於 list 初始化語法:
class Foo {
explicit Foo(int x, double y);
...
}
void Func(Foo f);
Func({42, 3.14}); // 發生錯誤
這類程式碼技術上來說並不算是隱性轉換,但是語言把它納入 explicit
關鍵字的適用範圍中。
優點
- 當型別已經很明顯時,隱性轉換可以省去明確標示型別的必要,使程式更容易使用且更明瞭。
- 隱性轉換可以當作比多載 (overloading) 更簡單的一種方案,例如一個接受
string_view
為引數的函式就可以同時代表接受string
與const char*
兩種型別的函式。 - List 初始化語法是一種簡潔明瞭的初始化做法。
缺點
- 隱性轉換會隱藏型別不合 (type-mismatch) 的 bug,像是目的型別不符合使用者的預期,或是使用者根本沒意識到有轉換發生。
- 隱性轉換會讓程式碼難以閱讀,尤其在有多載函式時,會讓實際呼叫的程式碼變得不明確。
- 單一引數的建構子可能會被意外地用於隱性型別轉換,就算這並非本意。
- 如果一個單一引數的建構子沒有標註
explicit
,並沒有一種可靠的方式能夠判斷到底是作者想要提供隱性轉換,還是只是單純忘記標註而已。 - 隱性轉換可能導致呼叫處產生混淆,特別是在雙向隱性轉換存在時。 這可能發生於兩邊的型別都有實作隱性轉換,或者一邊的型別同時實作了隱性建構函式與隱性型別的轉換函式。
- List 初始化在目的型別不明確時也會遭遇相同問題,特別是在 list 中只有一個元素的時候。
決定
型別轉換運算子與以單一引數呼叫的建構子必須在類別的定義中標註 explicit
。 一個例外是,複製與轉移建構函式不應該是 explicit
,因為他們並不會進行型別轉換。
隱性轉換在某些時候對於可以互換的型別來說可能是必要且恰當的,例如當某兩種型別其實只是底層數值的不同表示方式時就是如此。 如果遇到這種情況,請聯繫你的專案領導來豁免這條規則。
對於無法使用單一引數呼叫的建構子可以省略 explicit
。 只接受單一 std::initializer_list
引述的建構函式也應該省略 explicit
,以支援複製初始化語法 (copy-initalization, 例如:MyType m = {1, 2};
)。