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 p1 = SmartPtr(new Bottom);
SmartPtr p2 = p1;
同一模板的不同實例之間是沒有繼承關系的,在編譯器看來AutoPtr
和AutoPtr
是完全不同的兩個類。 所以上述代碼直接編譯是有問題的。
為了支持用SmartPtr
初始化SmartPtr
,我們需要重載SmartPtr
的構造函數。 原則上講,有多少類的層級我們就需要寫多少個重載函數。因為類的層級是會擴展的,因此需要重載的函數數目是無窮的。 這時便可以引入成員函數模板了:
template
class SmartPtr{
public:
template
SmartPtr(const SmartPtr& other);
};
注意該構造函數沒有聲明為
explicit
,是為了與普通指針擁有同樣的使用風格。子類的普通指針可以通過隱式類型轉換變成基類指針。
接受同一模板的其他實例的構造函數被稱為通用構造函數(generalized copy constructor)。
事實上,通用構造函數提供了更多的功能。他可以把一個SmartPtr
隱式轉換為SmartPtr
,把一個SmartPtr
轉換為SmartPtr
。 但普通指針是不允許這些隱式轉換的。因此我們需要把它們禁用掉。注意一下通用構造函數的實現方式便可以解決這個問題:
template
class SmartPtr{
public:
template
SmartPtr(const SmartPtr& other): ptr(other.get()){};
T* get() const{ return ptr; }
private:
T *ptr;
};
在ptr(other.get())
時編譯器會進行類型的兼容性檢查,只有當U
可以隱式轉換為T
時,SmartPtr
才可以隱式轉換為SmartPtr
。 這樣就避免了不兼容指針的隱式轉換。
除了隱式類型轉換,成員函數模板還有別的用途,例如賦值運算符。。下面是shared_ptr
的部分源碼:
template
class shared_ptr{
public:
template
explicit shared_ptr(Y *p);
template
shared_ptr const& r>;
template
shared_ptr& operator=(shared_ptr const& r);
};
可以看到普通指針Y*
到shared_ptr
聲明了explicit
,需要顯式的類型轉換;而shared_ptr
之間只需要隱式轉換。 使用拷貝構造函數模板存在一個問題:編譯器是會生成默認的拷貝構造函數?還是會從你的模板實例化一個拷貝構造函數? 即Y == T
場景下的編譯器行為。
事實上,成員函數模板不會改變C++的規則。C++規則講:如果你沒有聲明拷貝構造函數,那麼編譯器應該生成一個。 所以Y == T
時拷貝構造函數不會從成員函數模板實例化,而是會自己生成一個。
所以shared_ptr
模板中還是手動聲明了拷貝構造函數:
template
class shared_ptr{
public:
shared_ptr(shared_ptr const& r);
template
shared_ptr(shared_ptr const& r);
shared_ptr& operator=(shared_ptr const& r);
template
shared_ptr& operator=(shared_ptr const& r);
};