2,More Effective C++——條款5(謹慎使用定製“型別轉換函式”)
阿新 • • 發佈:2018-12-13
1 隱式型別轉換
C++中允許如下3種形式的隱式型別轉換:
1. 基本型別隱式型別轉換: int a = 10; double b = a; 2. 單引數構造建構函式 class Name { // 可以將char* 型別轉換成Name型別 Name(const char* str) {} }; class Apple { // 可以將int型別轉換成Apple型別 Apple(int a, double b = 10); }; 3,自定義隱式型別轉換操作符:關鍵字operator後加上型別名稱。該函式無返回型別。 class Banana { Banana(int a, int b) {} operator double() const; // 將Banana轉換成double型別 } Banana ba(1, 2); double b = 2 + ba; // ba隱式轉換成double型別
2 帶來的問題
1 問題一
隱式型別轉換會帶來一些我們所不期望、或者想不到的副作用。比如下面情況。顯然我們需要為Banana類過載一個“<<”操作符,但是由於Banana類可以隱式轉換成double型別,即便不過載"<<"操作符,下面的輸出語句也可以成功執行。
Banana ba(1, 2)
std::cout << ba;
因此,我們最後為Banana類,直接定義一個型別轉換函式。每次需要Banana類的double型別時,直接呼叫該函式即可。相應地,STL中string型別也無法隱式轉換成const char*型別。
double Banana::toDouble(){ }
2 問題二
下面程式碼展示了,單一建構函式導致型別轉換的問題。由於List類定義了傳入一個int型別的建構函式,因此b[index]作為一個int型別,可以轉換成List型別,從而呼叫了List的過載函式:==。變成兩個List物件進行比較。
template<class T> class List { public: List(int size) {} T& operator[] (int index); bool operator ==(const List<int>& lh, const List<int>& rh) ; } List<int> a(10), b(10); if (a == b[0]) { // b[index]進行型別轉換成了一個List類物件 // do somthing else }
3 解決隱式型別轉換帶來的問題
有兩種方式可以解決,上面提出的單一引數建構函式帶來的隱式型別轉換問題。
1 使用explicit關鍵字
C++引入了explicit修飾符,來修飾單一引數建構函式帶來的隱式型別轉換問題。explicit禁止隱式型別轉換,但是允許顯式型別轉換。
template<class T> class List {
explicit List(int size) {}
......
}
List<int> a(10), b(10);
if (a == b[1]) ... // 非法,型別不匹配
if (a == List<int>(b[1])) ... // 合法,定義一個新List物件進行比較
if (a == static_cast<List<int> >(b[0]))...... // 合法,但是無意義
if (a == (List<int>)(b[0]))...... // 合法,但是無意義
2 使用內部類防止型別轉換
C++不允許進行兩次隱式型別轉換,比如下面程式碼就是非法的:int轉換成List,List又轉換成Vector。(一次轉換路徑可以確認,對於兩次轉換路徑,無法知道第二次轉換應該走那一條路徑)
class List {
public:
List(int size);
}
class Vector {
public:
Vector(const List& list);
}
List list(10);
Vector vector(list);
if (list == 10)..... // 合法,單一引數建構函式造成隱式型別轉換
if (vector == 10) .... // 非法,連續兩次型別轉換
下面程式碼將List的長度"int size"封裝到內部型別ListSize中。初始化時,int可隱式轉換成ListSize型別,對List進行初始化。而a == b[1]進行比較時,由於int無法進行兩次轉換成List,因此編譯錯誤。
template<class T> class List {
public:
class ListSize {
public:
ListSize(int size) : m_size(size){}
int size() {return m_size;}
private:
int m_size;
}
List(ListSize size); // 此處將自定義型別作為引數傳入,從而避免基本型別進行隱式轉換
};
bool operatr ==(const List<int>& lh, const List<int>& rh);
List<int> a(10), b(10); // 合法,int型別轉換成ListSize, 用ListSize對List進行初始化
if (a == b[1]) .... // 非法,int型別只能轉換成ListSize型別,不能轉換成List型別