一、設計一個類不能被繼承
方法1:
利用靜態成員函數。。
由於一個類不能被繼承,也就是該類的建構函式不能被外界訪問到,那麼就需要將該類的構造和解構函式聲明為私人的,
而要調用一個類的任意一個非靜態成員變數或者函數都需要對象來調用(需要this指標),但是我們在外界又不能直接建立該類的對象,
所以就想到了靜態成員變數和函數的優點,不需對象也可以調用,所以我們可以定義一個靜態成員函數,在該靜態函數內部完成對象的建立,
然後將建立的對象返回,這樣我們在類外部可以通過類的範圍限定符來調用該類的靜態成員函數來獲得該類的對象。
但是這種方法有個缺陷:建立的對象都是在堆上建立的。不能再棧上建立對象。
代碼如下:
//設計一個類不能被繼承class sealedClass1{public: static sealedClass1* Getinst() { return new sealedClass1(); } static void destory(sealedClass1* p) { if (p) { delete p; p = NULL; } }private: int _a; sealedClass1() :_a(0) {} ~sealedClass1() {}};
方法2.:
能不能設計一個和一般類在功能上沒有差別而不能被繼承的類呢。
引用:虛繼承
需要一個輔助類(Tmp),將其建構函式和解構函式都聲明為私人的,然後再設計一個不能被繼承的類sealedClass ,(該類虛繼承這個輔助的類),並且該類還必須是這個輔助類的友元類,這樣的話該類才可以調用輔助類的建構函式。
現在假設我們設計的這個類sealedClass 可以被類C繼承,由於sealedClass 類是虛繼承Tmp類的,那麼類C就必須能夠調用tmp的建構函式,這可以從類C的物件模型中得出,而又因為類C不是Tmp類的友元類,所以C類不能直接調用Tmp類的建構函式,所以使得類sealedClass不能被繼承。
class sealedClass;class Tmp{public: friend sealedClass;private: Tmp() :_a(0) {} ~Tmp() {} int _a;};class sealedClass : virtual public Tmp{public: sealedClass() :_b(1) {} ~sealedClass() {}private: int _b;};class C :public sealedClass{public: C() :_c(3)//出現編譯錯誤 {}private: int _c;};
在C++中,類的對象建立分為兩種:
一種是靜態建立:如A a;
另一種是動態建立:如A* ptr=new A;
這兩種方式是有區別的。
靜態建立一個類對象,是由編譯器為對象在棧空間中分配記憶體,是通過直接移動棧頂指標,挪出適當的空間,然後在這片記憶體空間上調用建構函式形成一個棧對象。使用這種方法,直接調用類的建構函式。
動態建立類對象,是使用new運算子將對象建立在堆空間中。這個過程分為兩步:
第一步是執行operator new()函數,在堆空間中搜尋合適的記憶體並進行分配;
第二步是調用建構函式構造對象,初始化這片記憶體空間。這種方法,間接調用類的建構函式。 二、設計一個類只能在堆上建立對象
思路:
我們容易想到將建構函式設為私人。在將建構函式設為私人之後,就無法在類外部調用建構函式來構造類對象,只能使用new運算子來建立對象。
然而,new運算子的執行過程分為兩步:
①調用operator new()運算子在堆上為對象開闢空間;
②會調用對象的建構函式使其在堆上為對象開闢出來的空間處構造一個對象。
很顯然,第②步還是要調用對象的建構函式,所以這種方法不可以。
當對象建立在棧上面時,是由編譯器來分配記憶體空間的,調用建構函式來構造棧對象。當對象的生命週期快結束時,編譯器會調用解構函式來釋放棧對象所佔的空間。
編譯器管理了對象的整個生命週期。如果編譯器無法調用類的解構函式,情況會是怎樣的呢。比如,類的解構函式是私人的,編譯器無法調用解構函式來釋放記憶體。
所以,編譯器在為類對象分配棧空間時,會先檢查類的解構函式的訪問性,其實不光是解構函式,只要是非靜態函數,編譯器都會進行檢查。如果類的解構函式是私人的,則編譯器不會在棧空間上為類對象分配記憶體。
因此,將解構函式設為公有的,類對象就無法建立在棧上了。
class A{public: A(int x = 0) :_a(x) {} void destory() { delete this; }private: ~A() {} int _a;};
當在棧上建立對象時(A a;),編譯器報錯,解構函式在類外不可訪問。
但是有個問題,這樣看起來很奇怪,用new建立的對象,沒有用delete來釋放,如果這裡用delete來釋放對象時,就會發現根本不能成功,原因是delete釋放對象空間時也做了兩件事:
①調用類的解構函式清理對象空間;
②調用operator delete()來釋放對象的空間。
由於解構函式為保護的,在類外不能訪問,所以不能使用這種方法釋放對象的空間。
所以我們就想到可以調用類的一個介面函數在堆上new出對象,再調用一個介面函數去釋放對象的空間。
我們都知道,調用類中的非靜態成員函數需要類對象,所以需要將介面函式宣告為靜態。
class A{public: static A* Getinst() { return new A(); } void destory() { delete this; }protected: A(int x = 0) :_a(x) {} ~A() {} int _a;}
三、 設計一個類只能在棧上建立對象
由上題的分析,我們知道,要使得建立一個對象是只能在棧上建立,也就是不能在堆上建立對象,即就是不能使用new操作符在在堆空間來new一個對象,那隻要禁用new操作符就可以了,這就好辦了,重載一個operator new(),設定該重載函數時私人的。
class A{public: A(int x = 0) :_a(x) {} ~A() {}private: void* operator new(size_t N) { return NULL; } void operator delete(void* p) {} int _a;};
小結:
1.設計不能被繼承的類,需要考慮到物件模型;
2.只能在棧上建立對象,將解構函式的訪問限定符設為私人的或者保護的;
3.只能在堆上建立對象,使得在類外部不能使用new操作符在對堆上開闢空間。