沒有學不會的C++:使用者自定義的隱式型別轉換

程式設計師在深圳發表於2019-03-16

C++ 中的型別轉換包含內建型別的轉換和使用者自定義型別的轉換,而這兩者都又可分為隱式轉換和顯示轉換,所以一共有如下四象限表格中的 A、B、C、D 四種情況

隱式轉換 顯示轉換
(casting)
內建型別轉換
(int, float ...)
A B
使用者自定義型別轉換
(類 vs 類; 類 vs 內建型別)
C D

本篇只討論隱式轉換,內建型別的隱式轉換舉例如下

char c = 'A';
int i = c;                 // 將 char 隱式轉換為 int,又稱 Integral promotion
char* pc = 0;              // int 轉換為 Null 指標
dog* pd = new yellowdog(); // 指標型別轉換,子類 yellowdog 指標轉換為父類 dog 指標
複製程式碼

使用者自定義的隱式轉換是本篇的重頭戲,一般我們說自定義隱式轉換,指兩方面內容:

  1. 利用接受單個引數的建構函式,可以將其他型別的物件轉換為本物件
  2. 使用型別轉換函式, 將本類的物件轉換為其他物件

兩者合起來可以構成一個雙向轉換關係,下面我們看一個例子

class dog {
public:
	dog(string name) {m_name = name;} 
	string getName() {return m_name;}
private:
	string m_name;
};

int main() {
  string dogname = "dog";
  dog d = dogname;
	cout << "my name is " << d.getName() << \n";
  return 0;
};
複製程式碼

上面例子中,dog(string name) {m_name = name;} 有兩層含義,除了建構函式外,它還可以作為隱式轉換函式,將 string 物件轉換為 dog 物件,可以看到我們把 dogname 賦給了 dog d,像這樣的賦值,通常是無意的行為,而且它觸犯了 is-a 原則。

***如果你不想該建構函式具備隱式轉換的特性,你應該使用 explicit 對該函式進行宣告

explicit dog(string) {m_name = name;}
複製程式碼

反過來,我們還可以定義一個轉換函式,將 dog 物件轉換為 string,如下

class dog {
    // ...
    operator string () const { return m_name; }
};
複製程式碼

這樣,下面的輸出可以簡化為:

cout << "my name is " << (string)d << \n";
複製程式碼

可以看到,自定義型別的隱式轉換很容易寫出和本意不符的程式碼,這些程式碼往往容易出錯,很明顯,這樣的設計算不上是好設計,相反,我們更應遵循這樣的設計原則:

當我們定義一個 api 介面,我們希望正確的使用這個 api 是一件很容易的事情,且它很難被錯誤使用(理想情況下,當你錯誤使用一個 api 時,它不能被編譯通過),而定義過多的轉換函式,卻很容易讓我們的 api 出錯

對於隱式型別轉換,應該記住 2 點

  1. 避免定義看上去不符合預期的轉換,例如將 string 轉換為一個 dog
  2. 避免定義雙向的型別轉換,A 可以向 B 轉換,但繼續實現 B 向 A 的轉換就過猶不及

當然,隱式轉換並不是一無是處,它仍然有存在的意義,下面我們來舉一個正面的隱式轉換的例子,當你的類是一個處理數字的型別時,例如:有理數類,就可以使用隱式轉換技術:

class Rational {
public:
	Rational(int numerator = 0, int denominator = 1)
		: num(numberator), den(denominator) {}
	int num;
	int den;
};
Rational operator*(const Rational& lhs, const Rational& rhs) {
	return Rational(lhs.num*rhs.num, lhs.den*rhs.den);
}
int main() {
    Rational r1 = 23;
    Rational r2 = r1 * 2;
    Rational r3 = 3 * r1;
}
複製程式碼

上面程式碼定義了一個有理數類 Rational,它的建構函式接受 2 個預設引數,分別代表分子和分母,給該建構函式傳遞一個引數時,Rational 具有隱式轉換的特性,所以我們可以直接將數字賦值給 Rational 物件,如:Rational r1 = 23;

為了避免雙向轉換,這裡並沒有定義將 Rational 轉換為 int 的轉換函式,而當我們想實現 Rational 物件和 int 之間自由的算術運算時,我們需要定義全域性的操作符過載,如上面的 operator* 定義了有理數的乘法雲算符。

參考:www.youtube.com/watch?v=S7M…

相關文章