多繼承中重寫不同基類中的虛函數
在C++多繼承體系當中,在派生類中可以重寫不同基類中的虛函數。
下面就是一個例子:
——————————————————————————————
例一:
class CBaseA
{
public:
virtual void TestA();
};
class CBaseB
{
public:
virtual void TestB();
};
class CDerived : public CBaseA, public CBaseB
{
public:
virtual void TestA(); // 重寫基類CBaseA中的虛函數TestA()
virtual void TestB(); // 重寫基類CBaseB中的虛函數TestB()
};
void Test()
{
CDerived D;
CBaseA *pA = &D;
CBaseB *pB = &D;
pA->TestA(); // 調用類CDerived的TestA()函數
pB->TestB(); // 調用類CDerived的TestB()函數
}
——————————————————————————————
可是,如果兩個基類中有一個相同原型的虛函數,例如下面這樣:
例二:
class CBaseA
{
public:
virtual void Test();
};
class CBaseB
{
public:
virtual void Test();
};
怎樣在派生類中重寫這兩個相同原型的虛函數呢?
也許這種情況並不常見,可是這種情況卻確實存在。比如說開發的時候使用的兩個
類庫是不同的廠商提供的,或者說這兩個類庫是由公司
的不同開發小組開發的。對前者來說,修改基類的接口是不可能的;對後者來說,修改
接口的代價很大。
如果在派生類中直接重寫這個虛函數,那麼2個基類的Test()虛函數都將被覆蓋。這
樣的話就只能有一個Test()的實現,而不是像前面的例
子那樣有不同的實現。
class CDerived : public CBaseA, public CBaseB
{
public:
virtual void Test();
};
void Test()
{
CDerived D;
CBaseA *pA = &D;
CBaseB *pB = &D;
// 下面2行代碼都將調用類CDerived的Test()函數
pA->Test();
pB->Test();
}
——————————————————————————————
爲了實現第一個例子中的那樣,在派生類CDerived中重寫不同基類中相同原型的虛
函數Test(),可以使用下面的方法。
首先,不需要對2個基類進行任何修改(在實際的開發當中,修改基類的可能性非常小)。
例三:
class CBaseA
{
public:
virtual void Test();
};
class CBaseB
{
public:
virtual void Test();
};
現在,爲這個繼承體系添加2箇中間類,分別從2個基類派生。
class CMiddleBaseA : public CBaseA
{
private:
// 真正的實現函數
// 設置爲純虛函數,在派生類裏必須實現
virtual void CBaseA_Test() = 0;
// 改寫繼承下來的虛函數
// 僅僅直接調用真正的實現函數
virtual void Test()
{
CBaseA_Test();
}
};
// 與類CMiddleBaseA採用相同的方法
class CMiddleBaseB : public CBaseB
{
private:
virtual void CBaseB_Test() = 0;
virtual void Test()
{
CBaseB_Test();
}
};
然後,類CDerived以上面2箇中間類作爲基類來派生。分別重寫上面2個基類中原型
不同的純虛函數,添加不同的實現代碼。
class CDerived : public CMiddleBaseA, public CMiddleBaseB
{
private:
// 重寫從中間類繼承下來的虛函數
virtual void CBaseA_Test(); // 這裏實際上是重寫CBaseA的Test()
virtual void CBaseB_Test(); // 這裏實際上是重寫CBaseB的Test()
};
void Test()
{
CDerived D;
CBaseA *pA = &D;
CBaseB *pB = &D;
// 調用類CBaseA的Test()函數
// 由於C++多態的特性,實際上調用的是類CDervied中的CBaseA_Test()函數
pA->Test();
// 調用類CBaseB的Test()函數
// 由於C++多態的特性,實際上調用的是類CDervied中的CBaseB_Test()函數
pB->Test();
}
現在以上面代碼中的pA->Test();這行代碼來說明上面的方案是怎麼實現的。
首先,由於虛函數Test()在類CBaseA的派生類CMiddleBaseA中被重寫,所以這行代
碼會去調用類CMiddleBaseA的Test()函數;
然後,類CMiddleBaseA的Test()函數會去調用實現函數CBaseA_Test();
最後,由於虛函數CBaseA_Test()在類CMiddleBaseA的派生類CDerived中被重寫,所
以真正調用的是類CDerived中的CBaseA_Test()函數。
同樣的道理,代碼pB->Test();實際上調用的是類CDervied中的CBaseB_Test()函數。
通過上面的方法就可以在C++多繼承中重寫不同基類中相同原型的虛函數。
另附一份解釋:
這是一個很好的問題!
我曾經輕視過它,但是現在我意識到這個問題其實很簡單,但是有些subtle。
看以下的代碼:
class GrandParent
{
public:
virtual void kick()
{
// kick the Parent
}
};
class Parent
{
private:
virtual void kick()
{
// kick the children
}
};
class Child
{
protected:
virtual void kick()
{
// kick the dog
}
};
void test(void)
{
GrandParent* p=new Child;
}
在這裏p->kick的絕對是dog。問題出在這裏:在public, protected, private等等權限只存
在於編譯時期,而不在運行時起。多態性則發生在運行時期而不在編譯時期。這樣,容易看
出,虛表的結構大致如下:
child structure:
vptr of Child
vptr of Parent
vptr of GrandParent
vtbl of Child:
ptr of Child::kick // NO PUBLIC/PROTECT/PRIVATE ACCESS INFORMATION IN
VTBL!
vtbl of Parent:
ptr of Parent::kick
vtbl of GrandParent
ptr of GrandParent::kick
在編譯時期,因爲p的typeid是GrandParent*,所以在使用p->kick時編譯器檢查
GrandParent中相應的內容,因爲p指針指向的是Children類產生的對象中的GrandParent部
分,發現爲public。於是編譯通過。這是一個靜態的過程。
p->kick的代碼在編譯之後,因爲運行時是動態識別對象類型的,這樣因爲p指向的具體數據
是Child類型的,所以在kick時傳遞的this指針的類型是Child*的,虛表結構中因爲對象的
類型是Child,這樣Child中的成員優先調用(此時沒有訪問權限檢查!!),然後就訪問了
(Child*)->kick。
這就是這個問題的解決之道。其實我在看這個問題的時候,首先想到的是
class Derive
{
public:
virtual void test()
{
if (typeid(this)==typeid(ParentA*)) ...
if (typeid(this)==typeid(ParentB*)) ...
}
};
但是沒有用。因爲this的類型在動態解析時得到的是自身的類型。