C++ 运算符重载(二) | 类型转换运算符,二义性问题
文章目錄
- 類型轉換運算符
- 概念
- 避免過度使用類型轉換函數
- 解決上述問題的方法
- 轉換為 bool
- 顯式的類型轉換運算符
- 類型轉換二義性
- 重載函數與類型轉換結合導致的二義性
- 重載運算符與類型轉換結合導致的二義性
類型轉換運算符
概念
類型轉換運算符(conversion operator)是類的一種特殊成員函數。負責將一個類類型的值轉換成其他類型。
operator type() const ;其中 type 表示某種類型。類型轉換運算符可以面向任意類型(除了 void 之外)進行定義,只要該類型能作為函數的返回類型。因此,我們不允許轉換成數組或者函數類型,但允許轉換成指針(包括數組指針及函數指針)或者引用類型。
一個類型轉換函數必須是類的成員函數;它不能聲明返回類型,形參列表也必須為空。類型轉換函數通常不應該改變待轉換對象的內容,因此,應該是const。
運用實例,定義一個簡單的類,令其表示 0~255 之間的一個整數:
構造函數將算術類型的值轉換成 SmallInt 對象,而類型轉換運算符將 SmallInt 對象轉換成 int:
盡管編譯器一次只能執行一個 我們定義的類型轉換(如上面的構造函數/類型轉換運算符),但可以將其搭配 內置類型轉換(如double可以轉換成int) 實現二次轉換。
// 內置類型轉換將 doulbe 實參轉換成 int SmallInt si = 3.14; // 調用 SmallInt(int) 構造函數,然后調用拷貝構造函數 // SmallInt 的類型轉換運算符將 si 轉換成 int si + 3.14; // 內置類型將所得的 int 繼續轉換成 double盡管類型轉換函數不負責指定返回類型,但實際上每個類型轉換函數都會返回一個對應類型的值:
避免過度使用類型轉換函數
例如,假設某個類表示 Date,我們也許會為它添加一個從 Date 到 int 的轉換。然而,類型轉換函數的返回值應該是什么?
- 一種可能的解釋是,函數返回一個十進制數,依次表示年、月、日,例如,July 30,1989 可能轉換為 int 值 19890730。
- 同時還存在另外一種合理的解釋,即類型轉換運算符返回的 int 表示的是從某個時間節點(比如 January 1,1970)開始經過的天數。
問題在于 Date 類型的對象和 int 類型的值之間不存在明確的一對一映射關系。因此在此例中,不定義該類型轉換運算符也許會更好。作為替代的手段,類可以定義一個或多個普通的成員函數以從各種不同形式中提取所需的信息。
對于類來說,定義向 bool 的類型轉換還是比較普遍的現象。
int i = 42; cin << i; // 如果向 bool 的類型轉換不是顯式的,則該代碼在編譯器看來是合法的因為 istream 本身并沒有定義 <<,所以本來代碼應該產生錯誤。然而,該代碼能使用 istream 的 bool類型轉換運算符 將 cin 轉換成 bool ,而這個 bool值 接著會被提升成 int 并用作內置的左移運算符的左側運算對象。這樣一來,提升后的 bool值(1或0) 最終會 被左移42個位置。 這一結果顯然與我們的預期大相徑庭。
解決上述問題的方法
轉換為 bool
- 標準庫的早期版本中,IO 類型定義了向 void* 的轉換規則,以求避免上述問題。
- 在 C++11 標準中,IO 標準庫通過定義一個向 bool 的顯式類型轉換實現同樣的目的。
其實我們在編程中經常用到 IO 類型定義的 operator bool :
while(std::cin >> value)為了對條件求值,cin 被 istream operator bool 類型轉換函數隱式地執行了轉換。如果 cin 的條件狀態是 good,則該函數返回為真;否則該函數返回為假。(這部分知識可以看我之前的博客)
向 bool 的類型轉換通常用在條件部分,因此 operator bool 一般定義成 explicit 的。
顯式的類型轉換運算符
為了防止上面第二點這樣的異常情況發生,我們可以使用 explicit 關鍵字。
當類型轉換運算符是顯式的時,我們也能執行類型轉換,不過必須通過顯式的強制類型轉換才可以。
該規定存在一個例外,即,如果表達式被用作條件,則編譯器會將顯式的類型轉換自動應用于它。 換句話說,當表達式出現在下列位置時,顯式的類型轉換將被隱式地執行:
- if 、while 及 do 語句的條件部分
- for 語句頭的條件表達式
- 邏輯非運算符(!)、邏輯或運算符(||)、邏輯與運算符(&&)的運算對象
- 條件運算符(? :)的條件表達式。
類型轉換二義性
如果類中包含一個或多個類型轉換,則必須確保在類類型和目標類型之間只存在唯一一種轉換方式。否則的話,我們編寫的代碼將很可能會具有二義性。
在兩種情況下可能產生多重轉換路徑:
通常情況下,不要為類定義相同的類型轉換,也不要在類中定義兩個及兩個以上轉換源或轉換目標是算術類型的轉換。
第一種情況舉例:
解決方法是顯式調用:
A a1 = f(b.operator A()); A a2 = f(A(b));第二種情況舉例:
我們使用兩個用戶定義的類型轉換時,如果轉換函數之前或之后存在標準類型轉換,則標準類型轉換將決定最佳匹配到底是哪個:
重載函數與類型轉換結合導致的二義性
有時會出現這種情況:
或這種情況:
雖然我們可以通過顯式地構造正確的類型而消除二義性:
manip(C(10)); // 調用 manip(const C&) manip2(E(double(10))); // 調用 manip2(const E&)但意味著程序的設計存在不足。
重載運算符與類型轉換結合導致的二義性
重載的運算符也是重載的函數。因此也遵從通用的函數匹配規則。例如,如果 a 是一種類類型,則表達式 a sym b 可能是:
a.operatorsym(b); // a 有一個 operatorsym 成員函數 operatorsym(a, b); // operatorsym 是一個普通函數和普通函數不同,我們無法通過調用的形式區分當前調用的是成員函數還是非成員函數。
舉個例子:
- 第一條加法語句接受兩個 SmallInt 值并執行 + 運算符的重載版本。
- 第二條加法語句具有二義性:因為我們可以把 0 轉換成 SmallInt,然后使用 SmallInt 的 +;或者把 s3 轉換成 int,然后對于兩個 int 執行內置的加法運算。
如果我們對同一個類既提供了轉換目標是算術類型的類型轉換,也提供了重載的運算符,則將會遇到重載運算符與內置運算符的二義性問題。
總結
以上是生活随笔為你收集整理的C++ 运算符重载(二) | 类型转换运算符,二义性问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 「常识」鈊字五行属什么
- 下一篇: 生活小妙招:苹果的妙用苹果、苹果皮在日常