欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

operator new在C++中的各種寫法總結(jié)

 更新時間:2013年09月12日 10:10:31   作者:  
這篇文章并不是一個綜合的手冊,而是一個C++中各種內(nèi)存分配方法的概述。它面向已經(jīng)很熟悉C++語言的讀者

乍一看,在C++中動態(tài)分配內(nèi)存很簡單:new是分配,delete是釋放,就這么簡單。然而,這篇文章講得要復(fù)雜一點,并且要考慮到自定義層次。這也許對簡單的程序并不重要,但對你在代碼中控制內(nèi)存卻是十分必要的,是否能寫一個自定義的分配器,某種高級內(nèi)存管理表或一個特定的垃圾回收機(jī)制。

這篇文章并不是一個綜合的手冊,而是一個C++中各種內(nèi)存分配方法的概述。它面向已經(jīng)很熟悉C++語言的讀者。

原生operator new

我們先從原生operator new開始??紤]如下代碼,它用來分配5個int型的空間并返回指向他們的指針[1]:

int* v = static_cast<int*>(::operator new(5 * sizeof(*v)));

當(dāng)像如上的調(diào)用,operator new扮演原生的內(nèi)存分配角色,類似malloc。上面等價于:

int* v = static_cast<int*>(malloc(5 * sizeof(*v)));

釋放用operator new分配的內(nèi)存用operator delete:

::operator delete(v);

你愿意永遠(yuǎn)用原生new和delete函數(shù)嗎?是,只在極少數(shù)不用,我在下面的文章中會論證的。為什么用它們而不用原來的可信的malloc和free呢?一個很充分的原因就是你想保持代碼在C++領(lǐng)域的完整性?;旌鲜褂胣ew和free(或malloc和delete)是很不可取的(big NO NO)。用new和delete的另一個原因是你可以重載(overload)或重寫(override)這些函數(shù),只要你需要。下面是個例子:

復(fù)制代碼 代碼如下:

void* operator new(size_t sz) throw (std::bad_alloc)
{
    cerr << "allocating " << sz << " bytesn";
    void* mem = malloc(sz);
    if (mem)
        return mem;
    else
        throw std::bad_alloc();
}

void operator delete(void* ptr) throw()
{
    cerr << "deallocating at " << ptr << endl;
    free(ptr);
}


通常,注意到new被用來給內(nèi)置類型,不包含用戶自定義new函數(shù)的類的對象,和任意類型的數(shù)組分配空間,使用的都是全局的運(yùn)算符new。當(dāng)new被用來為已經(jīng)被重定義new的類實例化時,用的就是那個類的new函數(shù)。

下面來看下帶new函數(shù)的類。

特定類的operator new
大家有時很好奇"operator new"和"new operator"的區(qū)別。前者可以是一個重載的operator new,全局的或者特定類或者原生的operator new。后者是你經(jīng)常用來分配內(nèi)存的C++內(nèi)置的new operator,就像:

Car* mycar = new Car;

C++支持操作符重載,并且我們可以重載的其中一個就是new。

下面是個例子:

復(fù)制代碼 代碼如下:

class Base
{
public:
    void* operator new(size_t sz)
    {
        cerr << "new " << sz << " bytesn";
        return ::operator new(sz);
    }

    void operator delete(void* p)
    {
        cerr << "deleten";
        ::operator delete(p);
    }
private:
    int m_data;
};

class Derived : public Base
{
private:
    int m_derived_data;
    vector<int> z, y, x, w;
};

int main()
{
    Base* b = new Base;
    delete b;

    Derived* d = new Derived;
    delete d;
    return 0;
}


打印結(jié)果:
new 4 bytes
delete
new 56 bytes
delete

在基類被重載的operator new和operator delete也同樣被子類繼承。如你所見,operator new得到了兩個類的正確大小。注意實際分配內(nèi)存時使用了::operator new,這是前面所描述過的原生new。在調(diào)用前面的兩個冒號很關(guān)鍵,是為了避免進(jìn)行無限遞歸(沒有它函數(shù)將一直調(diào)用自己下去)。

為什么你要為一個類重載operator new?這里有許多理由。

性能:默認(rèn)的內(nèi)存分配算符被設(shè)計成通用的。有時你想分配給一個非常特殊的對象,通過自定義分配方式可以明顯地提高內(nèi)存管理。許多書和文章都討論了這種情況。尤其是"Modern C++ Design"的第4章展示了一個為較小的對象的非常好的設(shè)計并實現(xiàn)了自定義的分配算符。

調(diào)試 & 統(tǒng)計:完全掌握內(nèi)存的分配和釋放為調(diào)試提供了很好的靈活性,統(tǒng)計信息和性能分析。你可將你的分配算符插入進(jìn)專門用來探測緩沖區(qū)溢出的守衛(wèi),通過分配算符和釋放算符(deallocations)的比較來檢測內(nèi)存泄漏,為統(tǒng)計和性能分析積累各種指標(biāo),等等。

個性化:對于非標(biāo)準(zhǔn)的內(nèi)存分配方式。一個很好的例子是內(nèi)存池或arenas,它們都使得內(nèi)存管理變得更簡單。另一個例子是某個對象的完善的垃圾回收系統(tǒng),可以通過為一個類或整個層面寫你自己的operators new和delete。

研究在C++中new運(yùn)算符是很有幫助的。分配是分兩步進(jìn)行:

1.  首先,用全局operator new指導(dǎo)系統(tǒng)請求原生內(nèi)存。
2.  一旦請求內(nèi)存被分配,一個新的對象就在其中開始構(gòu)造。

The C++ FAQ給出一個很好的例子,我很愿意在這里這出來:

當(dāng)你寫下這段代碼:

Foo* p = new Foo();

編譯器會生成類似這種功能的代碼:

復(fù)制代碼 代碼如下:

Foo* p;

 // don't catch exceptions thrown by the allocator itself

//不用捕捉分配器自己拋出的異常

 void* raw = operator new(sizeof(Foo));

 // catch any exceptions thrown by the ctor

//捕捉ctor拋出的任何異常

 try {
   p = new(raw) Foo();  // call the ctor with raw as this 像這樣用raw調(diào)用ctor分配內(nèi)存
 }
 catch (...) {
   // oops, ctor threw an exception 啊哦,ctor拋出了異常
   operator delete(raw);
   throw;  // rethrow the ctor's exception 重新拋出ctor的異常
 }


其中在try中很有趣的一段語法被稱為"placement new",我們馬上就會討論到。為了使討論完整,我們來看下用delete來釋放一個對象時一個相似的情況,它也是分兩步進(jìn)行:

1.首先,將要被刪除對象的析構(gòu)函數(shù)被調(diào)用。
2.然后,被對象占用的內(nèi)存通過全局operator delete函數(shù)返還給系統(tǒng)。

所以:
delete p;
等價于[2]:
if (p != NULL) {
  p->~Foo();
  operator delete(p);
}

這時正適合我重復(fù)這篇文章第一段提到的,如果一個類有它自己的operator new或 operator delete,這些函數(shù)將被調(diào)用,而不是調(diào)用全局的函數(shù)來分配和收回內(nèi)存。

Placement new
現(xiàn)在,回來我們上面看到樣例代碼中的"placement new"問題。它恰好真的能用在C++代碼中的語法。首先,我想簡單地解釋它如何工作。然后,我們將看到它在什么時候有用。

直接調(diào)用 placement new會跳過對象分配的第一步。也就是說我們不會向操作系統(tǒng)請求內(nèi)存。而是告訴它有一塊內(nèi)存用來構(gòu)造對象[3]。下面的代碼表明了這點:

復(fù)制代碼 代碼如下:

int main(int argc, const char* argv[])
{
    // A "normal" allocation. Asks the OS for memory, so we
    // don't actually know where this ends up pointing.
    //一個正常的分配。向操作系統(tǒng)請求內(nèi)存,所以我們并不知道它指向哪里
    int* iptr = new int;
    cerr << "Addr of iptr = " << iptr << endl;

    // Create a buffer large enough to hold an integer, and
    // note its address.
    //創(chuàng)建一塊足夠大的緩沖區(qū)來保存一個整型,請注意它的地址
    char mem[sizeof(int)];
    cerr << "Addr of mem = " << (void*) mem << endl;

    // Construct the new integer inside the buffer 'mem'.
    // The address is going to be mem's.
    //在緩沖區(qū)mem中構(gòu)造新的整型,地址將變成mem的地址
    int* iptr2 = new (mem) int;
    cerr << "Addr of iptr2 = " << iptr2 << endl;

    return 0;
}


在我的機(jī)器上輸出如下:

Addr of iptr = 0x8679008
Addr of mem = 0xbfdd73d8
Addr of iptr2 = 0xbfdd73d8

如你所見,placement new的結(jié)構(gòu)很簡單。而有趣的問題是,為什么我需要用這種東西?以下顯示了placement new在一些場景確實很有用:

· 自定義非侵入式內(nèi)存管理。當(dāng)為一個類重載 operator new 同時也允許自定義內(nèi)存管理,這里關(guān)鍵概念是非侵入式。重載一個類的 operator new需要你改變一個類的源代碼。但假設(shè)我們有一個類的代碼不想或者不能更改。我們?nèi)绾稳阅芸刂扑姆峙淠兀?Placement new就是答案。這種用 Placement new達(dá)到這個目的的通用編程技術(shù)叫做內(nèi)存池,有時候也叫arenas[4]。

· 在一些程序中,在指定內(nèi)存區(qū)域的分配對象是很必要的。一個例子是共享內(nèi)存。另一個例子是嵌入式程序或使用內(nèi)存映射的周邊驅(qū)動程序,這些都可以很方便地在它們的“領(lǐng)地”分配對象。

· 許多容器庫預(yù)先分配很大一塊內(nèi)存空間。當(dāng)一個對象被添加,它們就必須在這里構(gòu)造,因此就用上了placement new。典型的例子就是標(biāo)準(zhǔn)vector容器。

刪除用placement new 分配的對象

一條C++箴言就是一個用new創(chuàng)建的對象應(yīng)該用delete來釋放。這個對placement new 同樣適用嗎?不完全是:

復(fù)制代碼 代碼如下:

int main(int argc, const char* argv[])
{
    char mem[sizeof(int)];
    int* iptr2 = new (mem) int;

    delete iptr2;       // Whoops, segmentation fault! 嗚啊,段錯誤啦!

    return 0;
}


為了理解上面代碼片段為什么delete iptr2會引起段錯誤(或某種內(nèi)存異常,這個因操作系統(tǒng)而異),讓我們回想下delete iptr2實際干了什么:

1.  First, the destructor of the object that's being deleted is called.

首先,調(diào)用將要被刪除的對象的析構(gòu)函數(shù)。

2.  Then, the memory occupied by the object is returned to the OS, represented by the global operator delete function.

然后,這個對象在操作系統(tǒng)中占用的內(nèi)存用全局operator delete函數(shù)收回。

對于用placement new分配的對象,第一步是沒有問題的,但第二步就可疑了。嘗試釋放一段沒有被分配算符實際分配的內(nèi)存就不對了,但上面的代碼確實這么做了。iptr2指向了一段并沒有用全局operator new分配的棧中的一段位置。然而,delete iptr2將嘗試用全局operator delete來釋放內(nèi)存。當(dāng)然會段錯誤啦。

那么我們應(yīng)該怎么辦?我們應(yīng)該怎樣正確地刪除iptr2?當(dāng)然,我們肯定不會認(rèn)為編譯器怎么會解決怎么翻譯內(nèi)存,畢竟,我們只是傳了一個指針給placement new,那個指針可能是從棧里拿,從內(nèi)存池里或者別的地方。所以必須手動根據(jù)實際情況來釋放。

事實上,上面的placement new用法只是C++的new指定額外參數(shù)的廣義placement new語法的一種特例。它在標(biāo)準(zhǔn)頭文件中定義如下:

復(fù)制代碼 代碼如下:

inline void* operator new(std::size_t, void* __p) throw()
{
    return __p;
}

C++一個對應(yīng)的帶有相同參數(shù)的delete也被找到,它用來釋放一個對象。它在頭文件中定義如下:
復(fù)制代碼 代碼如下:

inline void  operator delete  (void*, void*) throw()
{
}

的確,C++運(yùn)行并不知道怎么釋放一個對象,所以delete函數(shù)沒有操作。

怎么析構(gòu)呢?對于一個int,并不真的需要一個析構(gòu)函數(shù),但假設(shè)代碼是這樣的:
char mem[sizeof(Foo)];
Foo* fooptr = new (mem) Foo;

對于某個有意義的類Foo。我們一旦不需要fooptr了,應(yīng)該怎么析構(gòu)它呢?我們必須顯式調(diào)用它的析構(gòu)函數(shù):
fooptr->~Foo();
對,顯式調(diào)用析構(gòu)函數(shù)在C++中是合法的,并且這也是唯一一種正確的做法[5]。

結(jié)論
這是一個復(fù)雜的主題,并且這篇文章只起到一個介紹的作用,對C++的多種內(nèi)存分配方法給出了一種“嘗鮮”。一旦你研究一些細(xì)節(jié)會發(fā)現(xiàn)還有許多有趣的編程技巧(例如,實現(xiàn)一個內(nèi)存池分配)。這些問題最好是在有上下文的情況下提出,而不是作為一個普通的介紹性文章的一部分。如果你想知道得更多,請查閱下面的資源列表。

資源
· C++ FAQ Lite, especially items 11.14 and 16.9

·  "The C++ Programming Language, 3rd edition" by Bjarne Stroustrup – 10.4.11

· "Effective C++, 3rd edition" by Scott Myers – item 52

·  "Modern C++ Design" by Andrei Alexandrescu – chapter 4

· Several StackOverflow discussions. Start with this one and browse as long as your patience lasts.

我仍會在operator new前面顯式地寫::(雙冒號),雖然這里并不是必須的。恕我直言,這是一個很好的做法,特別當(dāng)在重載operator new的類中,可以避免二義性。

[2]

注意到這里是檢查是否為NULL。這樣做使delete 很安全,即使pNULL。

[3]

對傳給placement new的指針確保有足夠的內(nèi)存分配給對象,并且確保它們正確地對齊,這都是你的應(yīng)該做的。

[4]

內(nèi)存池本身是一個很大且迷人的話題。我并不打算在這里擴(kuò)展,所以我鼓勵你自己上網(wǎng)找些信息,WIKI如往常一樣是個好地方(good start)。

[5]

事實上,標(biāo)準(zhǔn)的vector容器用這種方法去析構(gòu)它保存的數(shù)據(jù)。

相關(guān)文章

  • C語言內(nèi)存分布與heap空間分別詳細(xì)講解

    C語言內(nèi)存分布與heap空間分別詳細(xì)講解

    一個程序本質(zhì)上都是由 BSS 段、data段、text段三個組成的。這種概念在當(dāng)前的計算機(jī)程序設(shè)計中是非常重要的一個基本概念,并且在嵌入式系統(tǒng)的設(shè)計中也非常重要,牽涉到嵌入式系統(tǒng)執(zhí)行時的內(nèi)存大小分配,存儲單元占用空間大小的問題
    2022-11-11
  • 實例講解C++ 命名空間

    實例講解C++ 命名空間

    這篇文章主要介紹了C++ 命名空間的的相關(guān)資料,文中示例代碼非常詳細(xì),供大家參考和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • Atom安裝配置C/C++詳細(xì)教程

    Atom安裝配置C/C++詳細(xì)教程

    Atom (一款開源的代碼編輯器)是github專門為程序員推出的一個跨平臺文本編輯器。這篇文章主要介紹了Atom安裝配置C/C++教程,需要的朋友可以參考下
    2020-05-05
  • c語言 跳臺階問題的解決方法

    c語言 跳臺階問題的解決方法

    本篇文章是對c語言中跳臺階問題的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • C++隨機(jī)數(shù)生成實例講解

    C++隨機(jī)數(shù)生成實例講解

    這篇文章主要為大家詳細(xì)介紹了C++隨機(jī)數(shù)生成實例,如何利用C++來生成0——N-1之間的隨機(jī)數(shù),感興趣的小伙伴們可以參考一下
    2016-04-04
  • C++實現(xiàn)LeetCode(237.刪除鏈表的節(jié)點)

    C++實現(xiàn)LeetCode(237.刪除鏈表的節(jié)點)

    這篇文章主要介紹了C++實現(xiàn)LeetCode(237.刪除鏈表的節(jié)點),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • C語言學(xué)生信息管理系統(tǒng)設(shè)計與實現(xiàn)

    C語言學(xué)生信息管理系統(tǒng)設(shè)計與實現(xiàn)

    這篇文章主要為大家詳細(xì)介紹了C語言學(xué)生信息管理系統(tǒng)設(shè)計與實現(xiàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • C語言代碼實現(xiàn)猜數(shù)字

    C語言代碼實現(xiàn)猜數(shù)字

    這篇文章主要為大家詳細(xì)介紹了C語言代碼實現(xiàn)猜數(shù)字,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-11-11
  • C++深入探究類與對象之對象模型與this指針使用方法

    C++深入探究類與對象之對象模型與this指針使用方法

    C++對象模型中只有類的非static成員以及一個指向虛函數(shù)表的指針被配置于類對象內(nèi),其他都在類對象外,在 C++ 中,每一個對象都能通過 this 指針來訪問自己的地址。this 指針是所有成員函數(shù)的隱含參數(shù)。因此,在成員函數(shù)內(nèi)部,它可以用來指向調(diào)用對象
    2022-04-04
  • 手把手帶你學(xué)習(xí)C++的數(shù)據(jù)類型

    手把手帶你學(xué)習(xí)C++的數(shù)據(jù)類型

    這篇文章主要為大家介紹了C++的數(shù)據(jù)類型,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助,希望能夠給你帶來幫助
    2021-11-11

最新評論