1. 程式人生 > 其它 >徹底理解JS的原型和原型鏈

徹底理解JS的原型和原型鏈

參考:

C++ 記憶體分配(new,operator new)詳解

如何限制物件只能建立在堆上或者棧上

new運算子和operator new()

new:指我們在C++裡通常用到的運算子,比如A* a = new A; 對於new來說,有new和::new之分,前者位於std

operator new():指對new的過載形式,它是一個函式,並不是運算子。對於operator new來說,分為全域性過載和類過載,全域性過載是void* ::operator new(size_t size),在類中過載形式 void* A::operator new(size_t size)。還要注意的是這裡的operator new()完成的操作一般只是分配記憶體,事實上系統預設的全域性::operator new(size_t size)也只是呼叫malloc分配記憶體,並且返回一個void*指標。而建構函式的呼叫(如果需要)是在new運算子中完成的

new和operator new之間的關係

A* a = new A;我們知道這裡分為兩步:1.分配記憶體,2.呼叫A()構造物件。事實上,分配記憶體這一操作就是由operator new(size_t)來完成的,如果類A過載了operator new,那麼將呼叫A::operator new(size_t ),如果沒有過載,就呼叫::operator new(size_t ),全域性new操作符由C++預設提供。因此前面的兩步也就是:1.呼叫operator new 2.呼叫建構函式。

(1)new :不能被過載,其行為總是一致的。它先呼叫operator new分配記憶體,然後呼叫建構函式初始化那段記憶體。

new 操作符的執行過程:
1. 呼叫operator new分配記憶體 ;
2. 呼叫建構函式生成類物件;
3. 返回相應指標。

(2)operator new:要實現不同的記憶體分配行為,應該過載operator new,而不是new。

operator new就像operator + 一樣,是可以過載的。如果類中沒有過載operator new,那麼呼叫的就是全域性的::operator new來完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以過載的。

如何限制物件只能建立在堆上或者棧上

在C++中,類的物件建立分為兩種,一種是靜態建立,如A a;另一種是動態建立,如A* ptr=new A;這兩種方式是有區別的。

靜態建立一個類物件,是由編譯器為物件在棧空間中分配記憶體,是通過直接移動棧頂指標,挪出適當的空間,然後在這片記憶體空間上呼叫建構函式形成一個棧物件。使用這種方法,直接呼叫類的建構函式。

動態建立類物件,是使用new運算子將物件建立在堆空間中。這個過程分為兩步,第一步是執行operator new()函式,在堆空間中搜索合適的記憶體並進行分配;第二步是呼叫建構函式構造物件,初始化這片記憶體空間。這種方法,間接呼叫類的建構函式。

那麼如何限制類物件只能在堆或者棧上建立呢?下面分別進行討論。

只能建立在堆上(設定解構函式為Protected)

類物件只能建立在堆上,就是不能靜態建立類物件,即不能直接呼叫類的建構函式。

容易想到將建構函式設為私有。在建構函式私有之後,無法在類外部呼叫建構函式來構造類物件,只能使用new運算子來建立物件。然而,前面已經說過,new運算子的執行過程分為兩步,C++提供new運算子的過載,其實是隻允許過載operator new()函式,而operator()函式用於分配記憶體,無法提供構造功能。因此,這種方法不可以。

當物件建立在棧上面時,是由編譯器分配記憶體空間的,呼叫建構函式來構造棧物件。當物件使用完後,編譯器會呼叫解構函式來釋放棧物件所佔的空間。編譯器管理了物件的整個生命週期。如果編譯器無法呼叫類的解構函式,情況會是怎樣的呢?比如,類的解構函式是私有的,編譯器無法呼叫解構函式來釋放記憶體。所以,編譯器在為類物件分配棧空間時,會先檢查類的解構函式的訪問性,其實不光是解構函式,只要是非靜態的函式,編譯器都會進行檢查。如果類的解構函式是私有的,則編譯器不會在棧空間上為類物件分配記憶體。

因此,將解構函式設為私有,類物件就無法建立在棧上了。程式碼如下:

class A  
{  
public:  
    A(){}  
    void destory(){delete this;}  
private:  
    ~A(){}  
};  

試著使用A a;來建立物件,編譯報錯,提示解構函式無法訪問。這樣就只能使用new操作符來建立物件,建構函式是公有的,可以直接呼叫。類中必須提供一個destory函式,來進行記憶體空間的釋放。類物件使用完成後,必須呼叫destory函式。

上述方法的一個缺點就是,無法解決繼承問題。如果A作為其它類的基類,則解構函式通常要設為virtual,然後在子類重寫,以實現多型。因此解構函式不能設為private。還好C++提供了第三種訪問控制,protected。將解構函式設為protected可以有效解決這個問題,類外無法訪問protected成員,子類則可以訪問。

另一個問題是,類的使用很不方便,使用new建立物件,卻使用destory函式釋放物件,而不是使用delete。(使用delete會報錯,因為delete物件的指標,會呼叫物件的解構函式,而解構函式類外不可訪問)這種使用方式比較怪異。為了統一,可以將建構函式設為protected,然後提供一個public的static函式來完成構造,這樣不使用new,而是使用一個函式來構造,使用一個函式來析構。程式碼如下,類似於單例模式:

class A  
{  
protected:  
    A(){}  
    ~A(){}  
public:  
    static A* create()  
    {  
        return new A();  
    }  
    void destory()  
    {  
        delete this;  
    }  
};  

這樣,呼叫create()函式在堆上建立類A物件,呼叫destory()函式釋放記憶體。

只能建立在棧上(過載new函式設為私有)

只有使用new運算子,物件才會建立在堆上,因此,只要禁用new運算子就可以實現類物件只能建立在棧上。將operator new()設為私有即可。程式碼如下:

class A  
{  
private:  
    void* operator new(size_t t){}     // 注意函式的第一個引數和返回值都是固定的  
    void operator delete(void* ptr){} // 過載了new就需要過載delete  
public:  
    A(){}  
    ~A(){}  
};