週二面了騰訊,以前只投了TST內推,貌似就是TST面試了面試
其中有一個問題,「如何產生一個不能被繼承的類」,這道題我反反覆覆只想到,將父類的構造函數私有,讓子類不能調用,最後歸結出一個單例模式,但面試官說,單例模式做爲此題的解答不夠靈活,後來面試官提示說,能夠用友元+虛繼承,能夠完美實現這樣一個類函數
固然那時我還不太明白,友元與虛繼承我都極少接觸過,只是知道有這些東西,回頭搜了一下「不能被繼承的類」的作法,具體以下:指針
1,聲明一個類,CNoHeritance,構造函數爲private,並聲明友元類CParent;
2,讓CParent虛繼承CNoHeritance
這樣CParent就成爲一個能夠被正常實例化,但又不能被繼承的類code
吳總當時評價說,「呵呵,虛繼承,感受徹底是黑科技啊」對象
這個黑科技真是戳中我笑點,但想到C++常常有些奇妙的東西,如今想總結一下繼承
對於閱讀過進階C++書籍的都該知道,編譯器會在「須要」的時候,那麼什麼是須要的時候呢?四種狀況:內存
自動合成的構造函數每每都是public,在派生類中,它的構造函數是能夠被使用的,即派生類不會所以受到限制。ci
那麼,如何能使派生類不能使用基類的函數或成員呢?編譯器
若是一個類的構造函數聲明爲private,則其派生類甚至該類的對象都不能訪問,意味着兩點:string
那麼怎麼使用該類呢?通常而言,會經過該類的函數來建立
class A { private: A(){} public: A& createA() { A* p=new A(); return *p; } };
然而,這樣又引伸一個問題:類沒有實例化,如何能使用其成員函數呢?
答案是將該成員函數聲明爲static,這樣不須要實例化便可訪問,即將上述改成:
class A { private: A(){} public: static A& createA() { A* p=new A(); return *p; } }; A Object=A::createA();
很明顯,上面的實例化過程很不方便,簡直是艱辛呀,單例模式的其中一種實現就是如此,在此先不講。這樣實現的類,不能被繼承,但本身也很差過
so,若是用友元來實現,是怎麼實現的呢?
聲明一個類,及其友元
class A { private: A(){} friend class B; };
那麼B是能夠調用A的private的構造函數的,那麼讓B虛繼承A會發生什麼事呢?
由《深度探索C++對象模型》看到,B內存中將有一份A類的實體,調用A的構造函數構造的,這對於友元類B是可行的
class A { private: A(){} friend class B; }; class B : virtual A { };
那麼這樣的B能不能被繼承呢?假設有個類繼承了B,以下
class A { private: A(){} friend class B; }; class B : virtual A { }; class C : B { };
考慮到虛繼承的特性,C也將調用A的構造函數構造出一個A,但!!C並非A的友元類,因此根本不能執行A私有的構造函數,這段程序,若是不實例化C,編譯器不會報錯,但一旦實例化C,則將報錯。
而B是能夠正常實例化的一個類,這樣就完美實現了一個不能被繼承的類:B
相比於構造函數的各類trick,C++的初始化列表就顯得很容易了,只有那麼一點要注意:
C++的初始化列表的賦值順序,是與C++類裏面成員變量的聲明順序相關,與初始化列表裏的順序無關
舉個例子,如下就會出現莫名錯誤:
class A { public: A(int _x, int _y):y(_y), x(y){} public: int x; int y; };
根據聲明順序,在初始化列表中,是先完成x(y)
這個步驟,但此時y
並無被賦值,因此獲得的x是個隨機的值。
C++虛函數的問題,幾乎是面試必問,實際上須要瞭解的東西也挺多,我本身在前幾回面試,都有些理解有誤的地方,或者理解不夠完善
這裏總結幾點吧(如下類都是針對有虛函數的類):
C++的拷貝構造函數是C++默認的四個函數之一:構造函數、析構函數、賦值函數、拷貝構造函數
拷貝構造函數是一種特別的構造函數,在《深度探索C++對象模型》書中說,有三種狀況,會致使拷貝構造函數被觸發:
class X {...} X x; X xx=x;
void foo(X x); X xx; foo(xx);
X foo_bar() { X xx; // ... return xx; }
通常狀況下,若是沒有提供explicit copy constructor時,會發生什麼呢?
一個良好的編譯器能夠爲大部分class objects產生bitwise copies,由於它們有bitwise semantics...
這裏說的很神奇,好像咱們不須要本身寫copy constructor也沒問題同樣,實際上,bitwise copies在有些狀況下是很是不推崇的
首先解釋下什麼是bitwise copies:這是指,在拷貝過來的時候,把class的內存直接位拷貝過來,便可以當作是內存拷貝(對應的有值拷貝)
位拷貝有不少問題,典型的一個,若是class裏面含有分配內存的指針,那麼它會將指針指向的地址直接拷貝過來:
class A { public: int *p; }; int main() { A a1; a1.p=new int[10]; A a2=a1; cout << a1.p << endl; cout << a2.p << endl; return 0; }
這裏能夠發現,a1.p
的地址與a2.p
的地址是同樣的,那麼,我分配的內存,該由哪一個釋放呢?我釋放了,另外一個怎麼辦呢?
實際上,這種拷貝方式在STL的string
裏面確定是要重寫的,不能用位拷貝。
《深度探索C++對象模型》中,說class不展示出「bitwise copy semantics」有四種狀況:
其實主要都是擔憂,指針在bitwise semantics下,隨便複製可能會致使不可預料的錯誤
在這裏說一下賦值函數與拷貝構造函數在觸發上的區別:
當一個object從無到有時,觸發的必定是拷貝構造函數,賦值函數只會在已有的object賦值時,纔會觸發
針對虛繼承,能夠坦承的一點就是
全部簡單的東西,遇到虛繼承,彷佛都要單獨拿出來討論
待續