程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> Item 46:需要類型轉換時,應當在類模板中定義非成員函數

Item 46:需要類型轉換時,應當在類模板中定義非成員函數

編輯:關於C++

Item 46: Define non-member functions inside templates when type conversions are desired.

Item 24中提到,如果所有參數都需要隱式類型轉換,該函數應當聲明為非成員函數。Item 24是以Rationaloperator*為例子展開的,本文把這個觀點推廣到類模板和函數模板。 但是在類模板中,需要所有參數隱式轉換的函數應當聲明為友元並定義在類模板中。

模板化的Rational

既然是Item 24的推廣,那麼我們先把Item24中的Rationaloperator*模板化。得到如下代碼:

template
class Rational {
public:
  Rational(const T& numerator = 0, const T& denominator = 1);
  const T numerator() const;           
  const T denominator() const;        
};

template
const Rational operator*(const Rational& lhs, const Rational& rhs){}

Item 20解釋了為什麼Rational的參數是常量引用;Item 28解釋了為什麼numerator()返回的是值而不是引用;Item 3解釋了為什麼numerator()返回的是const

模板參數推導出錯

上述代碼是Item24直接模板化的結果,看起來很完美但它是有問題的。比如我們有如下的調用:

Rational oneHalf(1, 2);            // OK
Rational result = oneHalf * 2;     // Error!

為什麼第二條會出錯呢?因為編譯器無法推導出合適的模板參數來實例化Rational。 模板參數的推導包括兩部分:

  • 根據onHalf,它的類型是Rational,很容易知道接受oneHalfoperator*中模板參數T應該是int
  • 根據2的模板參數推導卻不那麼順利,編譯器不知道如何將實例化operator*才能使得它接受一個int類型的2

    可能你會希望編譯器將2的類型推導為Rational,再進行隱式轉換。但在編譯器中模板推導和函數調用是兩個過程: 隱式類型轉換發生在函數調用時,而在函數調用之前編譯器需要實例化一個函數。而在模板實例化的過程中,編譯器無從推導T的類型。

    聲明為友元函數

    為了讓編譯器知道T是什麼,我們可以在類模板中通過friend聲明來引用一個外部函數。

    template
    class Rational {
    public:
        friend const Rational operator*(const Rational& lhs, const Rational& rhs);
    };
    
    template
    const Rational operator*(const Rational& lhs, const Rational& rhs){}
    

    Rational中聲明的friend沒有添加模板參數T,這是一個簡便寫法,它完全等價於:friend const Rational operator*(const Rational& lhs, const Rational& rhs);

    因為類模板實例化後,T總是已知的,因而那個friend函數的簽名會被Rational模板類聲明。 這樣,result = oneHalf * 2便可以編譯通過了,但鏈接會出錯。 雖然在類中聲明了friend operator*,然而編譯器卻不會實例化該聲明對應的函數。 因為函數是我們自己聲明的,那麼編譯器認為我們有義務自己去定義那個函數。

    在類中給出定義

    那我們就在聲明operator*時直接給出定義:

    template
    class Rational {
    public:
        friend const Rational operator*(const Rational& lhs, const Rational& rhs){
            return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
        }
    };
    

    這樣混合模式的調用result = oneHalf * 2終於可以編譯、鏈接並且運行了。到這裡想必問題已經很清楚了:

    1. 為了對所有參數支持隱式類型轉換,operator*需要聲明為非成員函數;
    2. 為了讓編譯器推導出模板參數,operator*需要在類中聲明;
    3. 在類中聲明非成員函數的唯一辦法便是聲明為friend
    4. 聲明的函數的同時我們有義務給出函數定義,所以在函數定義也應當放在friend聲明中。

      調用輔助函數

      雖然operator*可以成功運行了,但定義在類定義中的函數是inline函數,見Item 30。 如果operator*函數體變得很大,那麼inline函數就不再合適了,這時我們可以讓operator*調用外部的一個輔助函數:

      template class Rational;
      template
      const Rational doMultiply(const Rational& lhs, const Rational& rhs);
      
      template
      class Rational{
      public:
          friend Rational operator*(const Rational& lhs, const Rational& rhs){
              return doMultiply(lhs, rhs);
          }
      };
      

      doMultiply仍然是不支持混合模式調用的,然而doMultiply只會被operator*調用。operator*將會完成混合模式的兼容,然後用統一的Rational類型參數來調用doMultiply

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved