1. 程式人生 > >Item 45:使用成員函式模板來接受所有相容的型別

Item 45:使用成員函式模板來接受所有相容的型別

Item 45: Use member function templates to accept “all compatible types”.

Item 13提到智慧指標可用來自動釋放堆中的記憶體,STL中的迭代器也是一種智慧指標,它甚至支援連結串列元素指標的++操作。 這些高階特性是普通指標所沒有的。本文以智慧指標為例,介紹成員函式模板的使用:

  • 成員函式模板可以使得函式可以接受所有相容的型別。
  • 如果你用成員函式模板聲明瞭拷貝建構函式和賦值運算子,仍然需要手動編寫普通拷貝建構函式和拷貝運算子。

隱式型別轉換

智慧指標雖然比普通指標提供了更多有用的特性,但也存在一些問題,比如我們有一個類的層級:

class Top{};
class Middle: public Top{};
class Bottom: public Middle{};

普通指標可以做到派生類指標隱式轉換為基類指標:

Top *p1 = new Bottom;
const Top *p2 = p1;

但如果是智慧指標,比如我們實現了SmartPtr,我們則需要讓下面程式碼經過編譯:

SmartPtr<Top> p1 = SmartPtr<Bottom>(new Bottom);
SmartPtr<const Top> p2 = p1;

同一模板的不同例項之間是沒有繼承關係的,在編譯器看來AutoPtr< Top>和AutoPtr< Bottom>是完全不同的兩個類。 所以上述程式碼直接編譯是有問題的。

過載建構函式

為了支援用SmartPtr< Bottom>初始化SmartPtr< Top>,我們需要過載SmartPtr的建構函式。 原則上講,有多少類的層級我們就需要寫多少個過載函式。因為類的層級是會擴充套件的,因此需要過載的函式數目是無窮的。 這時便可以引入成員函式模板了:

template<typename T>
class SmartPtr{
public:
    template<typename U>
    SmartPtr(const SmartPtr<U>& other);
};
注意該建構函式沒有宣告為explicit,是為了與普通指標擁有同樣的使用風格。子類的普通指標可以通過隱式型別轉換變成基類指標。

接受同一模板的其他例項的建構函式被稱為通用建構函式(generalized copy constructor)。

相容型別檢查

事實上,通用建構函式提供了更多的功能。他可以把一個SmartPtr隱式轉換為SmartPtr,把一個SmartPtr轉換為SmartPtr。 但普通指標是不允許這些隱式轉換的。因此我們需要把它們禁用掉。注意一下通用建構函式的實現方式便可以解決這個問題:

template<typename T>
class SmartPtr{
public:
    template<typename U>
    SmartPtr(const SmartPtr<U>& other): ptr(other.get()){};
    T* get() const{ return ptr; }
private:
    T *ptr;
};

在ptr(other.get())時編譯器會進行型別的相容性檢查,只有當U可以隱式轉換為T時,SmartPtr才可以隱式轉換為SmartPtr。 這樣就避免了不相容指標的隱式轉換。

其他使用方式

除了隱式型別轉換,成員函式模板還有別的用途,例如賦值運算子。。下面是shared_ptr的部分原始碼:

template<class T> 
class shared_ptr{
public:
    template<class Y>
        explicit shared_ptr(Y *p);
    template<class Y>
        shared_ptr<shared_ptr<Y> const& r>;
    template<class Y>
        shared_ptr& operator=(shared_ptr<Y> const& r);
};

可以看到普通指標Y*到shared_ptr聲明瞭explicit,需要顯式的型別轉換;而shared_ptr之間只需要隱式轉換。 使用拷貝建構函式模板存在一個問題:編譯器是會生成預設的拷貝建構函式?還是會從你的模板例項化一個拷貝建構函式? 即Y == T場景下的編譯器行為。

**事實上,成員函式模板不會改變C++的規則。**C++規則講:如果你沒有宣告拷貝建構函式,那麼編譯器應該生成一個。 所以Y == T時拷貝建構函式不會從成員函式模板例項化,而是會自己生成一個。

所以shared_ptr模板中還是手動聲明瞭拷貝建構函式:

template<class T>
class shared_ptr{
public:
    shared_ptr(shared_ptr const& r);
    template<class Y>
        shared_ptr(shared_ptr<Y> const& r);

    shared_ptr& operator=(shared_ptr const& r);
    template<class Y>
        shared_ptr& operator=(shared_ptr<Y> const& r);
};