1. 程式人生 > >C++的三種new簡介及過載區域性(類內部)與全域性operator new

C++的三種new簡介及過載區域性(類內部)與全域性operator new

先簡單解釋下C++的三種new,由於它們的名字實在是。。我就說的通俗點。1、new運算子(new operator)大哥,我們在程式碼中直接使用的就是它。它做2件事:呼叫後兩種new申請記憶體和初始化物件。2、operator new,是一個函式,所以也是三種new唯一一個可以過載的。它類似C語言中的malloc,用於分配記憶體。3、placement new,用於初始化物件(如果有的話,就是它呼叫建構函式)。它還有其他的一些用法,下面具體介紹的時候再說。

1)過載全域性operator 

先看程式碼:

void * operator new(size_t size)
{
cout << "記憶體分配";
return malloc(size);
}


int main()
{
int * p = new int();
cin.get();
return 0;
}

輸出:記憶體分配

        這樣貌似是過載了operator new,可能你還在懷疑就這麼幾行程式碼,僅僅只是返回一片記憶體塊,用它替換原先的operator new是否能正確完成一個物件的構造。答案是可以。你可以嘗試new一個物件試試,不會有任何問題。要記住,三種new 各自分工,它唯一需要做的只是分配記憶體,只要記憶體分配對了,一切OK。

        2)過載區域性(類內部)operator new

還是先看程式碼:

class A
{
public :
A()
{
cout << "物件構造\n";
}
void * operator new(size_t size)
{
cout << "記憶體分配\n";
return malloc(size);
}
};


int main()
{
A *p = new A();
cin.get();
return 0;
}

先後輸出:記憶體分配,物件構造。

        1、這是類內部的過載,就像過載運算子一樣,僅對於這個類有效。所以如果你像這樣int *p = new int();不會輸出"記憶體分配"。此時new 會去呼叫全域性的operator new。
        2、第一點說過,這個operator new僅在類內部有效,所以它和new運算子所呼叫的全域性operator new是2個不同的函式。所以如果你把上面程式碼中的  return malloc(size);  改成  return ::operator new(size);  可以達到同樣的效果。因為同樣只是完成記憶體的分配,這裡把malloc換成全域性的operator new成為純C++的語法,或許更嚴謹一些吧。

        3、可能有人會想,既然可以把malloc換成全域性的operator new,能不能直接把return malloc(size);  改成  return ::new A();  這個就有意思了,既然類過載的operator new只有可以看作是劫持全域性operator new的功能,何不直接分配記憶體也不讓它管了,劫持之後剩下的工作讓全域性new和全域性operator new去完成。

    所以呼叫順序是這樣的:new運算子 -> 過載operator new -> new運算子 -> 全域性operator new -> 第二個new運算子呼叫的placement new -> 第一個new運算子呼叫的int i

    看起來有些複雜,輸出結果是這樣的,記憶體分配,物件構造,物件構造。

    沒錯,雖然在類過載的operator new裡輸出了分配記憶體,但實際上只有全域性operator new真正去做分配記憶體。然後2次呼叫的運算子new呼叫了2次的placement new。但是這樣做目前為止到沒發現什麼問題,但實際上物件構造了2次,實在是沒有什麼實際意義,而且在某些情況下可能會引起嚴重的錯誤。比如在建構函式裡做一些其它的記憶體分配工作。

      4、可能又有人想了,能不能把過載全域性operator new中的return malloc(size);  改成  return ::operator new(size);   這是不行的,因為此時全域性的operator new就是你過載了的operator new了,這樣改寫執行程式,你將會看到無數的 "記憶體分配"瘋狂的輸出,沒錯,它遞迴呼叫自身了。這就跟把過載類operator new中的return malloc(size);  改成  return operator new(size);一樣,你是在讓它呼叫自己。

        再簡單介紹一下placement new,前面說過,它是在operator new分配的記憶體上初始化這片區域,呼叫建構函式。我們也可以直接來使用它,你手上有一塊記憶體p。int *p2 = new (p) int(100);  這樣你就用placement new把一塊記憶體初始化為int,並給它個初值100。其實這裡,p所指向的記憶體可以是堆也可以是棧。如果是棧記憶體被這樣new,不需要delete,且可以反覆的new。這有什麼用, 你可以把動態分配記憶體的請求限制在一塊區域內,這就有點像作業系統給引用程式分配記憶體,這一塊給你隨便用, 妨礙不到其他的記憶體。算是一種安全策略吧。

           最後再說一下:

operator new 會多申請sizeof(int)大小的記憶體用來儲存這段空間的大小。

placement new 如果在申請一個數據的時候也要多申請sizeof(int)大小的空間用來儲存陣列的大小。eg,

void *ptr = operatro new(sizeof(int)*100);

int *iPtr = new(ptr)int[100];//如果這樣,那麼這裡將會出錯的,因為你實際上系統是申請了100+sizeof(int)的空間!然而ptr上面只有100*szieof(int)的大小,所以申請空間失敗。

上述二者都是new運算子在呼叫operator new 時傳引數size自動為我們加上的,我們在過載operator new以及使用new時不需要擔心這個問題。

         另外,本文中沒有提到delete,delete[]以及new []過載,new []的過載就不贅述了。至於delete,三種new其實也對應著不同的三種delete:new運算子對應delete,operator new和placement new處理的記憶體都可以使用operator delete來釋放,但是釋放之前則需要我們手動呼叫解構函式。因為呼叫解構函式是delete運算子幫我們呼叫的,不直接使用delete的話則需要手動呼叫解構函式。

        其實過載operator new的目的並不是我們需要多麼高階的記憶體分配或物件構造技術,而是有時我們需要跟蹤記憶體的分配,給它劫持一下,做個程式計數什麼的,你甚至可以用過載,做一個記憶體管理器,Java或.NET的垃圾回收器那種聽起來很高大上的東西,尤其是在記憶體管理這一塊,在面試的時候跟HR吹一把能給你加不少分。

       很久以前學的知識(趁我忘了之前趕緊寫進部落格),如果有什麼地方不對,歡迎各位牛人批評指教,想要深入研究的朋友可以去讀一下more effective C++,裡面講的很詳細。

--------------------- 

作者:Koma丶 
來源:CSDN 
原文:https://blog.csdn.net/qq_29227939/article/details/51638241 
版權宣告:本文為博主原創文章,轉載請附上博文連結!

 

==================================

new運算子過載

==================================

首先我們要清楚,為什麼我們要過載new,和delete了?這還不是指標造成的,確實指標是一件讓人喜歡的東西,用起來如此讓人喜歡,讓人順手。然而小程式我們完全可以避免記憶體洩露問題,大程式就不那麼容易了,然而我們有一種特別好的方法可以跟蹤我們new,和delete動作,找到未被釋放的記憶體。辦法是什麼呢?微軟給我們提供了一種很好的方法,那就是過載new,和delete。

       在實現之前我們要清楚new,和delete的工作機理,這是個多麼好的學習機會啊!

       對與new操作符,其實和sizeof一樣,都是c++內建的,然而像strlen就不是了,strlen屬於函式。對於new的功能我們是沒有辦法改變的,當我們new一個物件時,new為我們做了兩件事情,一、申請一塊足夠的記憶體空間供存放物件,對於new一個數組物件,編譯器會計算出總共的空間,然後執行類似c語言中malloc函式類似的功能。二、初始化物件,對於單個物件,包括包括基本物件和類物件,可以通過括號初始化,比如int * pn = new int(3); A * pa = new A(3);   然而對於陣列不能初始化,對於類物件,必須定義無引數的建構函式。對與new的基本功能我們是無法改變的。

       我們所能改變的就是如何為物件分配記憶體,我們可一過載這個函式以實現分配記憶體。通常的過載方式是:

       void * operator new (size_t size);

       然而這已經足夠了,在一般的operator new 過載函式裡,我們可以再加入其它引數,但第一個引數必須是size_t型別,即為無符號整形。正是有這種特性,為我們對記憶體申請和釋放的跟蹤提供了可能。


       具體實現就是第一個operator new 的過載函式,我們第一的這個函式是這樣的:

void * operator new(unsigned int size, const char *file, int line)
{
    cout << "new size:" << size << endl;
    cout << file << " " << line << endl;

    void * p = malloc(size);

    return p;
}

       然後用巨集替換所有的new:

#define new new(__FILE__, __LINE__)

       這樣我們每次呼叫new,比如int * pn = new int;被編譯器替換成了int * pn = new (__FILE__, __LINE__) int,從而呼叫我們定義的operator new,這種辦法確實很妙。需要交代的是,對於陣列同樣適用,而是在編譯的是後由編譯器計算出所需要的長度呼叫我們定義的operator new函式。

       對於delete,我們無需使用巨集定義,只要過載operator delete就可以了,然而我們需要過載兩個delete:

void operator delete(void * p)
{
    cout << "delete " << (int)p << endl;
    free(p);
}

void operator delete [] (void * p)
{
    cout << "delete [] " << (int)p << endl;
    free(p);
}


       其實後面一個函式的內容和前面的內容一樣,但為了支援陣列的釋放,我們必須是要定義的。那麼我們只是簡單的free(p)編譯器咋知道我們釋放的陣列,還是單個物件了,這個不用我們操心了,我們只要提供給free函式一個申請記憶體的首地址就可以了。因為在用malloc申請的時候,我們也把陣列裝換為記憶體大小了。

       由此我們可以得出下面的推斷:用int * pn = new int [100];的空間,用delete pn釋放和delete [] pn釋放效果一樣。然而那麼既然這兩種方式一樣,那還要delete [] 幹什麼,其實delete [] 有另外的作用就是類物件陣列的釋放,編譯器把delete []解釋為呼叫物件的解構函式,這個是通過迴圈實現的,最後釋放掉申請的記憶體。

       既然我們已經跟蹤了new和delete,那麼就可以比較容易的判斷申請的記憶體是否最後得到釋放,要完成它,我們還需要一個連結串列,或者其它,當我們申請一塊記憶體的時候加入到連結串列中,釋放一塊空間的時候,從連結串列中刪除和釋放記憶體首地址相同的節點就可以了,最後理想的情況是連結串列為空,如果不為空,那就說明記憶體發生洩露(Memory leaks)了。

完整程式碼:

#include "malloc.h"
#include "iostream.h"

#ifdef _DEBUG

void * operator new(unsigned int size, const char *file, int line)
{
    cout << "new size:" << size << endl;
    cout << file << " " << line << endl;

    // 下面兩種方法可以達到同樣的效果,但下面一種比較好
    // 因為用下面一種可以保持原有的申請方式一樣
    //void * p = malloc(size);
    void * p = operator new(size);

    return p;
}

void operator delete(void * p)
{
    cout << "delete " << (int)p << endl;
    free(p);
}

void operator delete [] (void * p)
{
    cout << "delete [] " << (int)p << endl;
    free(p);
}

void operator delete(void * p, const char *file, int line)
{
    cout << "delete file line" << endl;
    free(p);
}

void operator delete [] (void * p, const char *file, int line)
{
    cout << "delete [] file line" << endl;
    free(p);
}

#define new new(__FILE__, __LINE__)
#endif

void main()
--------------------- 
作者:ghevinn 
來源:CSDN 
原文:https://blog.csdn.net/ghevinn/article/details/18359519 
版權宣告:本文為博主原創文章,轉載請附上博文連結!