Explicit——謹慎定義隱式類型轉換函數
在查找別的資料的時候,看到這么一個關鍵字,以前都沒見過覺得挺有用,
於是找來More Effective C++進行學習總結一下。
一 隱式轉換
C++編譯器能夠在兩種數據類型之間進行隱式轉換(implicit conversions),它繼承了C語言的轉換方法。
隱式把char——>int和從short——>double。轉換可能會導致數據的丟失。
自定義的類型時,你就可以有更多的控制力,因為你能選擇是否提供函數讓編譯器進行隱式類型轉換。
有兩種函數允許編譯器進行這些的轉換:單參數構造函數(single-argument constructors)和隱式類型轉換運算符。
二 隱藏的隱式轉換
1 單參數構造函數是指只用一個參數即可以調用的構造函數。
該函數可以是只定義了一個參數,也可以是雖定義了多個參數但第一個參數以后的所有參數都有缺省值。
什么什么情況會發生隱式類型轉換,和構造函數參數類型有關。
Example1:
class Name { // for names of things
public:
Name(const string& s); // 轉換 string 到
{ // Name
...
};
char ch = 'a';
string str;
Name name(str);
name = str; //Right str會隱式轉換為Name類型,會執行其構造函數
name = ch; //Error Name構造函數參數類型為string 而不是char
Example2:
class Rational
{ // 有理數類
public:
Rational(int numerator = 0, // 轉換int到
int denominator = 1) // 有理數類Rational
{
}
Rational(Name name) // 轉換Name類型到 有理數類Rational
{
}
Rational(string str) // 轉換string類型 到有理數類
{
}
};
char ch = 'a';
string str;
Name name(str);
int i = 3;
Rational ra;
//以下均Right
ra = i; // 執行Rational(int numerator = 0,int denominator = 1)轉換
ra = ch; //執行Rational(int numerator = 0,int denominator = 1)轉換
ra = str; //執行Rational(string str)轉轉
ra = name; //執行Rational(Name name)轉換
從以上兩個例子可以看到:在單參數構造函數中隱式轉換很容易發生。
2 隱式類型轉換運算符
就是 operator 關鍵字,其后跟一個類型符號。
class Rational
{ // 有理數類
public:
……
operator double() const // 轉換Rational類成 double類型
{
}
operator string () const // 轉換Rational類成 string類型
{
}
};
str = ra; // 執行operator string ()轉換string 運行崩潰
i = 1*ra; //執行operator double()轉換成double型
cout<<ra; //執行operator double() 轉換成double型輸出隨機浮點數
所以避開類型轉換運算符!
三 隱式轉化的消除
1 隱式類型轉換運算符
不提供這種operator轉換運算符。使用等同的函數來替代轉換運算符;
用asDouble函數代替operator double函數。
double asDouble() const; //轉變 Rational 成double
cout << ra.asDouble(); //相當於提供一個接口
2 單參數構造函數進行隱式類型轉換消除
template<class T>
class Array
{
public:
Array(int lowBound, int highBound){} //不能做隱式類型轉換
Array(int size) //可以做隱式類型轉換
{
m_size = size;
m_array = new T(size);
}
T& operator[](int index)
{
return m_array[index];
}
bool operator==( const Array<int>& rhs)
{
//成員重載函數只能單參數
}
friend bool operator==(const Array<int>& lhs,const Array<int>& rhs)
{
//這個函數是有問題的,比較的應該是數值,但卻成了Array對象比較
if (lhs == rhs) //又會調用friend bool operator 死循環
{
return 1;
}
return 0;
}
private:
int m_size;
T* m_array;
};
Array<int> a(10);
Array<int> b(10);
for (int index = 0; index < 10; ++index)
{
if (a == b[index]) //執行friend bool operator 崩潰
{
// 哎呦! "a" 應該是 "a[i]"
//do something for when
//a[i] and b[i] are equal;
}
else
{
//do something for when they're not;
}
}
//實際中
for (int i = 0; i < 10; ++i)
if (a == static_cast< Array<int> >(b[i])) //每次比較都會轉換
一不小心就會,碰上這種隱式轉換問題,且不容易發覺問題所在。
解決這個問題:
一是使用關鍵字explicit (這個需要編譯器支持!)
編譯器會拒絕為了隱式類型轉換而調用構造函數。顯式類型轉換依然合法。
class Name
{ // for names of things
public:
explicit Name(const string &s) // 轉換 string 到 Name
{
}
};
string str;
Name name(str);
name = static_cast<Name> (str); //Right 顯式轉換
name = str; //Error explicit禁止隱式類型轉換
二是 更改構造函數參數
因為類中隱式類型轉換僅限於單參數構造函數中。且和構造函數的參數類型有關。
所以可以對構造函數的參數類型進行修改。通常沒有一個轉換能夠包含用戶自定義類型。
對於剛才的例子class Array中構造函數Array(int size)是可以做隱式類型轉換
對參數size包裝成,我們自定義類型。
將size交給內置的ArraySize類來管理。
template<class T>
class Array
{
public:
class ArraySize
{ // 這個類是新的
public:
ArraySize(int numElements): theSize(numElements) {}
int size() const { return theSize; }
private:
int theSize;
};
Array(int lowBound, int highBound);
Array(ArraySize size); // 注意新的聲明
...
};