創建不能被繼承的類?只在棧上?只在堆上?

【不被繼承】

首先想到的是在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 
        
}; 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章