虛函數具有動態聯編性,在類族中有強大功能;友元函數具有跨類訪問的功能,本質卻是一種對封裝的破壞。
先看這樣一個例子:
#include<iostream> using namespace std; class A; class B { private: int x; void print() { cout<<x<<endl; } public: B(int i = 0) { x = i; } friend class A; }; class A { public: void func(B b) { b.print(); } }; class D: public B { public: D(int i):B(i) {} }; int main() { cout<<sizeof(A)<<" "<<sizeof(B)<<" "<<sizeof(D)<<endl; D d(99); A a; a.func(d); return 0; }
程序執行結果為:
1 4 4
99
上例中,A是B的友元類,A中的所有成員函數都為B的友元函數,可訪問B的私有成員函數。友元類A大小為1,基類和派生類大小都是4,友元類A不是基類B的一部分,更不是派生類D的一部分。
從上例看,友元似乎能夠被繼承,A的函數func這能訪問B的派生類D嘛!這不基類的友元函數或友元類能夠訪問派生類的私有成員!
但若將上例中的繼承關系改為私有繼承,則:
class D: private B a.func(d); // error C2243: “類型轉換”: 從“D *”到“const B &”的轉換存在,但無法訪問
我們知道:public繼承是一種“is a”的關系,即一個派生類對象可看成一個基類對象。所以,上例中不是基類的友元被繼承了,而是派生類被識別為基類了。
再比如這樣一個例子
#include<iostream> using namespace std; class B; class A { private: void print() { cout<<"A::print"<<endl; } public: friend class B; }; class B { public: void func(A a) { a.print(); } }; class D: public B { }; int main() { A a; D d; d.func(a); return 0; }
程序執行結果為:
A::print
上例中,B為A的友元類,D是B的派生類,D繼承了基類B的友元函數func,它能訪問A的私有成員。由此可知一個友元類的派生類,可以通過其基類接口去訪問設置其基類為友元類的類的私有成員,也就是說一個類的友元類的派生類,某種意義上還是其友元類。
但若在上例D中新增加個成員函數,該函數是不能訪問A私有成員的。
class D: public B { public: void test(A a){ a.print(); } // error C2248: “A::print”: 無法訪問 private 成員(在“A”類中聲明) };
#include<iostream> using namespace std; class A; class B { private: void print() { cout<<"B::print"<<endl; } public: friend class A; }; class A { public: void func(B b) { b.print(); } }; class D: public B { private: void print() { cout<<"D::print"<<endl; } }; int main() { D d; A a; a.func(d); return 0; }
程序執行結果為:
B::print
和前兩例類似,友元關系並沒有被繼承,僅是派生類對象當成了一個基類對象來用,因此輸出“B::print”。
若將上例print函數改為虛函數並通過多態來訪問,就可以達到類似於友元可以繼承的效果。
class A; class B { private: virtual void print() { cout<<"B::print"<<endl; } public: friend class A; }; class A { public: void func(B* pb) { pb->print(); } }; class D: public B { private: virtual void print() { cout<<"D::print"<<endl; } }; int main() { D d; A a; a.func(&d); return 0; }
這本質上就是滿足了多態的三個條件:
必須存在繼承關系;
繼承關系中必須有同名的虛函數,並且它們是覆蓋關系。
存在基類的指針,通過該指針調用虛函數。