effective C++ 條款 46:需要類型轉換時請為模板定義非成員函數

來源:互聯網
上載者:User

條款24說過為什麼惟有non-member函數才有能力“在所有實參身上實施隱式類型轉換”。本條款將Rational和operator*模板化:

template<typename T>
class Rational{
    Rational(const T& number = 0,
        const T& denominator = 1);
    const T number() const;
    const T denominator() const;
};
template<typename T>
const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs)
{......}

像條款24一樣,我們希望支援混合算術運算,我們希望一下代碼順利通過編譯:

Rational<int> oneHalf(1,2);
Rational<int> result = oneHalf * 2;//錯誤!無法通過編譯

條款24內,編譯器知道我們嘗試調用什麼函數(就是接受兩個Rationals參數的那個operator*),但這裡編譯器不知道我們想要調用哪個函數。取而代之的是,他們試圖想出什麼函數被名為operator*的template具現化(產生)出來。它們知道應該可以具現化某個“名為operator*並接受兩個Rational<T>參數”的函數,但完成這一具現化行動,必須先算出T是什麼。問題是它們沒這個能耐。

為了推導T,它們看了看operator*調用動作中的參數類型。每個參數分開考慮。

以oneHalf進行推導,過程並不困難。T一定是int。第二個參數被聲明是Rational<T>,但傳遞給operator*的第二個實參(2)類型是int。編譯器如何根據這個推算出T?你或許會期盼編譯器使用Rational<int>的non-explicit建構函式將2轉換成Rational<int>,進而將T推導成int。但它們不那麼做,因為在template實參推導過程中從不將隱式類型轉換函式納入考慮。絕不!這樣的轉換在函數調用過程中的卻被使用,但在能夠調用一個函數之前,首先必須知道那個函數存在。為了知道它,必須先為相關的function template推匯出參數類型(然後才可將適當的函數具現化出來)。template實參推導過程中並不考慮採納“通過建構函式而發生的”隱式類型轉換。

有方法可以緩和編譯器在template實參推導方面受到的挑戰:template class內的friend聲明式可以指涉某個特定函數。

class template並不依賴template實參推導(後者只施行於function template身上),所以編譯器總是能在class Rational<T>具現化時得知T,所以令Rational<T> class 聲明適當的operator*為其friend函數,可簡化整個問題:

template<typename T>
class Rational{
public:
    friend
        const Rational operator* (const Rational& lhs, const Rational& rhs);    //聲明operator*函數
};
template<typename T>
const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs)
{......}

現在對operator*的混合式調用可以通過編譯了。因為當對象oneHalf被聲明為一個Rational<int>,class Rational<int>於是被具現化出來,而作為過程的一部分,friends函數operator*(接受Rational<int>參數)也就被自動聲明出來。後者身為一個函數而非函數模板,因此編譯器可在調用它的時候使用隱式轉換函式(例如Rational的non-explicit建構函式)。

在一個class template中,template名稱可被用來作為“template和其參數”的簡略表達方式,所以在Rational<T>內,我們可以唯寫Rational而不必寫Ratioan<T>。和如下聲明一樣:

template<typename T>
class Rational{
public:
    friend
        const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs);    //聲明operator*函數
};

然而使用簡略表達方式(速記式)比較輕鬆也比較普遍。

這個函數只被聲明於Rational內,並沒有被定義出來。我們意圖令此class外部的operator* template提供定義式,但是行不通。我們在class Rational template內聲明了一個函數,就有責任定義那個函數。

或許最簡單的可行方法就是將operator*函數本體合并至其聲明式內:

template<typename T>
class Rational{
public:
    friend
        const Rational operator* (const Rational& lhs, const Rational& rhs);    //聲明operator*函數
    {
        return Rational(lhs.numerator() * rhs.numerator(),
                                lhs.denominator() * rhs.denominator());
    }
};

我們雖然使用friend,卻與friend的傳統用途“訪問class的non-public成分”毫不相干。為了讓類型轉換可能發生在所有參數身上,我們需要一個non-member函數:為了令這個函數被自動具現化,我們需要將它聲明在一個class內部:而在class內部聲明non-member函數的唯一辦法就是:令他成為一個friend。

定義於class內的函數都暗自成為inline,包括operator*這樣的friend函數。你可以將這樣的inline聲明所帶來的衝擊最小化,做法是令operator*不做任何事情,只調用一個定義於class外部的輔助函數。Rational是個template意味著這個輔助函數通常也是一個template。Rational標頭檔代碼,很典型的長這個樣子:

template<typename T> class Rational;        //聲明Rational template
template<typename T>
const Rational<T> doMultiply (const Rational<T>& lhs, const Rational<T>& rhs); //聲明helper template

template<typename T>
class Rational{
public:
    ...
friend
        const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs);
    {
        return doMultiply(lhs, rhs);        //令friend調用helper
    }
};

許多編譯器實質上會強迫你把所有template定義式放進標頭檔內,所以你或許需要在標頭檔內定義doMultiply(條款30,這樣的templates不需非得是inline不可),看起來像這樣:

template<typename T>
const Rational<T> doMultiply (const Rational<T>& lhs, const Rational<T>& rhs)
{
    return Rational<T>(lhs.numerator() * rhs.numerator(),
                                    lhs.denominator() * rhs.denominator());
}

作為一個template,doMultiply當然不支援混合式乘法,但它其實也不需要。它只被operator*調用,而operator*支援混合式操作!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.