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);
};