1. 程式人生 > >2,More Effective C++——條款5(謹慎使用定製“型別轉換函式”)

2,More Effective C++——條款5(謹慎使用定製“型別轉換函式”)

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型別