1、傳入基類,繼承實現
在設計com接口時,經常會遇到這樣的情況:設計一個基接口,其他多個接口繼承該接口。一個典型的例子是IUnknown接口,所有的com接口必須從IUnknown接口繼承,而這些接口的實現都是相同的,我們不可能爲每一個com接口寫一個IUnknown接口的實現。IUnknown接口的實現比較複雜,分佈在幾個類中(可參考《深入解析atl》)。比較直觀的一個例子是IDispatch接口的實現,通常需要實現IDispatch接口的類都從IDispatchImpl繼承。這個結構是這樣的:
class ATL_NO_VTABLE Cbbb :
public IDispatchImpl<Ibbb, &IID_Ibbb, &LIBID_AAAALib>
template <class T, ...>
class ATL_NO_VTABLE IDispatchImpl : public T
這樣,每個需要實現IDispatch接口的類都只需要從IDispatchImpl繼承即可。
IDispatchImpl的做法其實相當簡單,僅僅是引入了一個實現類而已。
另一種常見的情況是分離接口和實現。比如有以下的接口繼承結構:
interface IBase
{
virtual void A()=0;
};
interface IDerive1 : public IBase
{
virtual void D1()=0;
};
interface IDerive2 : public IBase
{
virtual void D2()=0;
};
IBase對應的實現類如下:
class CBase : public IBase
{
virtual void A(){...}
};
這裏假設有一個類CDerive1需要繼承接口IDerive1和實現類CBase,如果直接從兩個類繼承,象這樣:
class CDerive1 : public CBase,public IDerive1
{
public:
void D1(){}
};
當使用CDerive1類時,編譯器會抱怨模棱兩可。很自然的,考慮到用虛擬繼承解決:
interface IDerive1 : virtual public IBase
{
virtual void D1()=0;
};
class CBase : virtual public IBase
{
public:
virtual void A(){...}
};
class CDerive1 : public CBase,public IDerive1
{
public:
void D1(){}
};
一切看起來都很完美,virtual繼承解決了模棱兩可問題。但是虛擬繼承是很多產生複雜性問題的根源,難以擴展和維護.
看來我們還是隻有老老實實加一箇中間層吧。最終解決方案如下:
template<typename TBase>
class CBase : public TBase
{
public:
virtual void A(){...}
};
class CDerive1 : public CBase<IDerive1>
{
public:
void D1(){}
};
2、傳入子類
在atl中,有大量的以下用法:
template<typename T>
class CB
{
public:
void Fun1()
{
T* pT = static_cast<T*>(this);
pT->FunD(); //調用T的函數
}
};
這裏傳進來的是CB的子類
class CD : public CB<CD>
{
public:
void FunD(){...}
};
這種技術實際上有點象虛函數.
以上語句T* pT = static_cast<T*>(this)之所以能編譯通過是因爲模板類CB在編譯期間已被實例化爲CB<CD>,編譯器已經知道T就是CD,而CD繼承自CB,所以從CB向CD轉型是安全的.這種手法的一個好處是不需要一個T類型的對象,直接可以使用this指針安全轉型.其2,避免了虛函數調用的開銷,雖然虛函數的開銷是很小的,單繼承情況下幾乎可以忽略不計,但是虛函數的存在不利於編譯器的優化,而且static和inline虛函數也要出問題.