C++ 允許類別定義轉換運算子(如 operator double())與單參數建構函數,編譯器會在需要時自動呼叫它們進行隱式轉換。雖然語法便利,卻極易引發難以察覺的邏輯錯誤。
class Rational {
public:
Rational(int num, int den) : n(num), d(den) {}
operator double() const { return static_cast(n) / d; } // 隱式轉換為 double
private:
int n, d;
};
Rational r(1, 2); // r = 1/2
double d = 0.5 * r; // 隱式呼叫 operator double(),結果 0.25 ✓
std::cout << r; // 期望輸出 "1/2",實際輸出 "0.5" ✗
因為 operator<< 找不到 Rational 多載,編譯器嘗試隱式轉換為 double 並呼叫 operator<<(double),導致輸出浮點數而非分數格式——這就是隱式轉換的「驚喜」。
class Rational {
public:
Rational(int num, int den) : n(num), d(den) {}
double asDouble() const { return static_cast(n) / d; } // 顯式轉換
// 移除 operator double()
private:
int n, d;
};
Rational r(1, 2);
std::cout << r; // 編譯錯誤:無 operator<<,強制開發者明確意圖
std::cout << r.asDouble(); // 正確:明確呼叫轉換函數
C++11 引入 explicit 修飾單參數建構函數,禁止編譯器為隱式轉換而呼叫它:
template
class Array {
public:
explicit Array(int size); // 禁止隱式 int -> Array
// ...
};
Array a(10); // 正確:直接建構
Array b = 10; // 編譯錯誤:隱式轉換被禁止
if (a == b[i]) ... // 編譯錯誤:無法隱式將 int 轉為 Array
if (a == Array(b[i])) // 正確:顯式轉換(雖邏輯可能不合理)
注意: static_cast<Array<int>>(b[i]) 中雙尖括號需加空格 > >,避免被解析為右移運算子。
建立專用型別 ArraySize 封裝整數,建構函數改受 ArraySize 參數:
template
class Array {
public:
class ArraySize { // 巢狀類別強調專用性
public:
explicit ArraySize(int n) : sz(n) {}
int size() const { return sz; }
private:
int sz;
};
Array(int low, int high);
Array(ArraySize size); // 唯一單參數建構函數
// ...
};
Array a(10); // int -> ArraySize -> Array,合法(直接建構)
if (a == b[i]) ... // 編譯錯誤:int 無法隱式轉為 ArraySize 再轉 Array
此技巧利用「使用者定義轉換序列最多僅能包含一個使用者定義轉換」規則,精準控制隱式轉換路徑。
explicit,採用 Proxy 類別隔離轉換路徑。