上一篇 從引用傳遞到設計模式 (上) 的文末,提到非虛擬介面 NVI 的實現,即將虛函數聲明為保護型或私有型,藉由模板函數模式來實現 。 園友 @KillU 看的很仔細,提出了一個問題:虛函數是 private 類型,繼承可以麽? 答案是:可以 5 實現權和調用權 <Effective C++> 中 ...
上一篇 從引用傳遞到設計模式 (上) 的文末,提到非虛擬介面 NVI 的實現,即將虛函數聲明為保護型或私有型,藉由模板函數模式來實現 。
園友 @KillU 看的很仔細,提出了一個問題:虛函數是 private 類型,繼承可以麽? 答案是:可以
5 實現權和調用權
<Effective C++> 中給的解釋是: 重寫一個虛函數,指的是如何做事情 (how), 而調用一個虛函數,指的是什麼時候做事情 (when)
NVI 或 模板函數模式中,允許派生類重寫虛函數,賦給派生類的是“實現權” - 即函數功能的實現;但是基類仍然掌握“調用權” - 即什麼時候調用虛函數
聽起來很是拗口,來看一個實例,還是模板函數模式的實現,只不過是將虛函數聲明為私有的 (private)
5.1 私有虛函數
基類 AbstractClass 和 派生類 ConcreteClass
class AbstractClass { public: void TemplateMethod(); private: virtual void PrimitiveOperation1(); virtual void PrimitiveOperation2(); }; class ConcreteClass : public AbstractClass { private: void PrimitiveOperation1() override; void PrimitiveOperation2() override; };
基類 AbstractClass 中,兩個虛函數 PrimitiveOperation1,PrimitiveOperation2 以及 TemplateMethod 的實現
// implementation of private member function void AbstractClass::PrimitiveOperation1() { std::cout << "Operation1 from AbstractClass !" << std::endl; } void AbstractClass::PrimitiveOperation2() { std::cout << "Operation2 from AbstractClass !" << std::endl; } // implementation of non-virtual member function void AbstractClass::TemplateMethod()
{ PrimitiveOperation1(); PrimitiveOperation2(); }
派生類 ConcreteClass 中,重寫兩個虛函數 PrimitiveOperation1 和 PrimitiveOperation2
// override void ConcreteClass::PrimitiveOperation1() { std::cout << "Operation1 from ConcreteClass !" << std::endl; } void ConcreteClass::PrimitiveOperation2() { std::cout << "Operation2 from ConcreteClass !" << std::endl; }
當通過指針或引用調用虛函數時,具體是調用基類還是派生類里的虛函數,取決於動態綁定到該指針或引用的類對象
// call virtual functions in AbstractClass AbstractClass* p1 = new AbstractClass; p1->TemplateMethod(); // call virtual functions in ConcretetClass AbstractClass* p2 = new ConcreteClass; p2->TemplateMethod();
輸出的結果如下:
Operation1 from AbstractClass !
Operation2 from AbstractClass !
Operation1 from ConcreteClass !
Operation2 from ConcreteClass !
這個結果,乍一看並不會立即產生疑問,因為派生類重寫了兩個虛函數,這兩個虛函數也是派生類自己的私有成員函數,調用自己類里的虛函數自然沒什麼問題。
5.2 虛函數不被重寫
下麵改變一下程式,派生類 ConcreteClass 內不重寫兩個虛函數,只是單純的繼承自基類 AbstractClass,如下所示:
class ConcreteClass : public AbstractClass { };
執行程式,輸出結果:
Operation1 from AbstractClass !
Operation2 from AbstractClass !
Operation1 from AbstractClass !
Operation2 from AbstractClass !
這時候疑問便出現了:怎麼派生類 ConcreteClass 居然可以訪問基類 AbstractClass 的私有虛函數? 這可是私有類型!
其實這是一種錯覺,並不是派生類直接調用了基類的私有虛函數,而是派生類的非虛成員函數 ConcreteClass::TemplateMethod ,
因為繼承自基類的非虛成員函數 AbstractClass::TemplateMethod,從而間接的調用了基類的私有虛函數。
實際的“調用權”依然牢牢握在基類手中,只不過是基類提供了一個 TempaltMethod 的介面 (interface),可以讓派生類來間接調用而已
5.3 私有非虛函數
再次修改程式,基類內 PrimitiveOperation1 和 PrimitiveOperation2 不再聲明為虛函數,也即基類從派生類手中收回了函數的“實現權”
class AbstractClass { public: void TemplateMethod(); private: void PrimitiveOperation1(); // non-virtual void PrimitiveOperation2(); // non-virtual };
同時派生類 ConcreteClass 中,聲明自己的私有成員函數 PrimitiveOperation1 和 PrimitiveOperation1,“隱藏”了基類中對應的同名函數
class ConcreteClass : public AbstractClass { private: void PrimitiveOperation1(); void PrimitiveOperation2(); };
執行程式輸出結果:
Operation1 from AbstractClass !
Operation2 from AbstractClass !
Operation1 from AbstractClass !
Operation2 from AbstractClass !
輸出的結果和 5.2 是一樣的,派生類調用 ConcreteClass::TemplateMethod,而 ConcreteClass::TemplateMethod 繼承自 AbstractClass::TemplateMethod,
因此,實際上,仍然還是基類內的非虛成員函數,調用基類內的私有成員函數
5.4 純虛函數
重申 5.1 中的一句話:當通過指針或引用調用虛函數時,具體調用基類還是派生類里的虛函數,取決於動態綁定到該指針或引用的類對象基類里的私有成員函數
可以這麼理解,虛函數涉及的是動態綁定,和它本身是公有、私有還是保護,並無多大關係
實際中應用中,既然要使用模板方法模式,那就是說必定要在派生類 ConcreteClass 中重寫 PrimitiveOperation1 和 PrimitiveOperation2
而要保證這兩個函數一定會被派生類重寫,可以將它們聲明為純虛函數,即在函數最末尾加 “= 0”,如下所示:
class AbstractClass { public: void TemplateMethod(); private: virtual void PrimitiveOperation1() = 0; virtual void PrimitiveOperation2() = 0; };
此時,因為基類內申明瞭純虛函數,所有基類 AbstractClass 變成了一個抽象基類(abstarct base class)
抽象基類只能提供介面 (interface),而不能實例化, 執行下麵代碼是會出錯的:
AbstractClass* p1 = new AbstractClass; // error ! p1->TemplateMethod();
小結:
1) NVI gives the derived class control over how functionality is implemented, but the base class reserves the right when the function will be called
2) Derived classes may redefine private inherited virtual functions
3) Pure virtual functions specify inheritance of interface only
參考資料:
<Effective C++> item 35
<C++ Primer_5th> 15.4 Abstract Base Classes
<Design Patterns> Template Method