【不被繼承】
首先想到的是在C++中,子類的構造函數會自動調用父類的構造函數。同樣,子類的析構函數也會自動調用父類的析構函數。要想一個類不能被繼承,只要把它的構造函數和析構函數都定義爲私有函數。那麼當一個類試圖從它那繼承的時候,必然會由於試圖調用構造函數、析構函數而導致編譯錯誤。
可是這個類的構造函數和析構函數都是私有函數了,怎樣才能得到該類的實例呢?可以通過定義靜態來創建和釋放類的實例。基於這個思路,可以寫出如下的代碼:
/////////////////////////////////////////////////////////////////////// class FinalClass1 { public : static FinalClass1* GetInstance() { return new FinalClass1; } static void DeleteInstance( FinalClass1* pInstance) { delete pInstance; pInstance = 0; } private : FinalClass1() {} ~FinalClass1() {} };這個類是不能被繼承,但在總覺得它和一般的類有些不一樣,使用起來也有點不方便。比如,只能得到位於堆上的實例,而得不到位於棧上實例。
那有沒有既能讓它不被繼承,除此之外可以和其他類一樣的方法呢?
#include <iostream> using namespace std; template <typename T> class Base{ friend T; private: Base(){ cout << "base" << endl; } ~Base(){} }; class B:virtual public Base<B>{ //一定注意 必須是虛繼承 public: B(){ cout << "B" << endl; } }; class C:public B{ public: C(){} //繼承時報錯,無法通過編譯 }; int main(){ B b; //B類無法被繼承 //C c; return 0; }
類Base的構造函數和析構函數因爲是私有的,只有Base類的友元可以訪問,B類在繼承時將模板的參數設置爲了B類,所以構造B類對象時們可以直接訪問父類(Base)的構造函數。
爲什麼必須是虛繼承(virtual)呢?
參見 c++Primer 4th 第17.3.7節 特殊的初始化語義
通常每個類只初始化自己的直接基類,但是在虛繼承的時候這個情況發生了變化,可能導致虛基類被多次初始化,這顯然不是我們想要的。(例2: AA,AB都是類A的派生類,然後類C又繼承自AA和AB,如果按之前的方法會導致C裏面A被初始化兩次,也會存在兩份數據)
爲了解決重複初始化的問題,從具有虛基類的類繼承的類在初始化時進行了特殊處理,在虛派生中,由最低層次的派生類的構造函數初始化虛基類。在我們上面的例1中就是由C的構造函數控制如何進行虛基類的初始化。
爲什麼B類不能被繼承?
回到例1,因爲B是Base的友元,所以B對象可以正常創建,但由於B使用了虛繼承,所以如果要創建C對象,那麼C類的構造函數就要負責虛基類(Base)的構造,但是Base的構造函數是私有的,C沒有訪問的權限(ps:友元關係不能被繼承的),所以例1中的C類在編譯時就會報錯。這樣B類就不能被繼承了。
【只在堆/棧上創建對象】
C++中,對象的建立分爲兩種,一種是靜態建立,如A a;另一種是動態建立,如 A *ptr=new A;
靜態建立:由編譯器爲對象在棧上分配內存,是通過直接移動棧頂指針,挪出適當的空間,然後在這片內存空間上調用構造函數形成一個棧對象,使用這種方法,直接調用類的構造函數
動態建立:是通過new運算符將對象建立在堆空間中,這個過程分爲兩步,第一步是執行operator new()函數,在堆空間中搜索合適的內存並進行分配,第二步是調用構造函數構造對象,初始化這片內存空間,這種方法,間接調用類的構造函數
接下來進行限制對象只能在那裏建立?
1.只能建立在堆上?
就是不能靜態的建立對象,即不能直接調用類的構造函數。
分析:
(1)我們容易想到將構造函數設爲私有,在構造函數私有之後,無法在類外部調用構造函數來構造類對象,只能使用new運算符來建立對象,然而,前面已經說過,new運算符的執行過程分爲兩步,c++提供new運算符的重載,其實是隻提供operator new() 函數,而operator()函數是進行內存分配的,無法提供構造功能,因此這種方法是不可以的
(2)對象建立在棧上面時,是由編譯器分配空間的,調用構造函數來構造棧對象,當對象使用完之後,編譯器會調用析構函數來釋放棧對象所佔的空間,編譯器管理了對象的整個生命週期,編譯器爲對象分配空間的時候,只要是非靜態的函數都會檢查,包括析構函數,但是此時析構函數不可訪問,編譯器無法調用類的析構函數來釋放內存,那麼編譯器將無法在棧上爲對象分配內存
class a{ public : a(){} void destory(){ delete this; } private: ~a(){}; };那可以嘗試使用a b;來建立對象,編譯報錯,提示析構函數無法訪問,這樣就只能使用new來創建對象,構造函數是共有的,可以直接調用,但是類中必須通過destory()函數來進行內存的釋放,類對象使用完之後,必須調用destory()函數
上述方法兩個缺點:(1)無法解決繼承問題,因爲通常情況之下a作爲基類,一般析構函數要設爲vitual,然後子類重寫,已實現多態,因此析構函數不能設爲private,不過c++還有protected訪問控制方式,將析構函數設置爲protected,這樣子類可以訪問,但是類外無法訪問。(2)使用不方便,不統一,因爲你使用了new創造了對象,但是不能使用delete釋放對象,必須使用destory函數,這種方式比較怪異,所以我們也可以將構造函數設置爲protected,同時提供另一public static create()函數來進行替代new。這樣 create()創建對象在堆上, destory()釋放內存
class a{ public : static a* create(){ return new a(); } void destory(){ delete this; } protected: a(){}; ~a(){}; };
2.只能建立在棧上
只有使用new操作符,纔會使對象建立在堆上,因此只要禁用new操作符就可以了,所以我們將operator new()操作符設爲保護或者私有就可以了
class a{ public : a(){}; ~a(){}; protected: void* operator new(size_t t){}; //重載new,注意參數以及返回值 void operator delete(void *ptr){}; //重載了new,就要重載delete };