日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > c/c++ >内容正文

c/c++

operator new在C++中的各种写法

發(fā)布時(shí)間:2024/4/11 c/c++ 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 operator new在C++中的各种写法 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

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

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

?

原生operator?new

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

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

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

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

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

::operator?delete(v);

你愿意永遠(yuǎn)用原生newdelete函數(shù)嗎?是,只在極少數(shù)不用,我在下面的文章中會(huì)論證的。為什么用它們而不用原來的可信的mallocfree呢?一個(gè)很充分的原因就是你想保持代碼在C++領(lǐng)域的完整性。混合使用newfree(或mallocdelete)是很不可取的(big?NO?NO)。用newdelete的另一個(gè)原因是你可以重載(overload)或重寫(override)這些函數(shù),只要你需要。下面是個(gè)例子:

?

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ù)的類的對(duì)象,和任意類型的數(shù)組分配空間,使用的都是全局的運(yùn)算符new。當(dāng)new被用來為已經(jīng)被重定義new的類實(shí)例化時(shí),用的就是那個(gè)類的new函數(shù)。

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

?

特定類的operator?new

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

Car*?mycar?=?new?Car;

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

下面是個(gè)例子:

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

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

?

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

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

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

?

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

1.??首先,用全局operator?new指導(dǎo)系統(tǒng)請(qǐng)求原生內(nèi)存。

2.??一旦請(qǐng)求內(nèi)存被分配,一個(gè)新的對(duì)象就在其中開始構(gòu)造。

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

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

Foo*?p?=?new?Foo();

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

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",我們馬上就會(huì)討論到。為了使討論完整,我們來看下用delete來釋放一個(gè)對(duì)象時(shí)一個(gè)相似的情況,它也是分兩步進(jìn)行:

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

2.??然后,被對(duì)象占用的內(nèi)存通過全局operator?delete函數(shù)返還給系統(tǒng)。

所以:

delete?p;

等價(jià)于[2]:

if?(p?!=?NULL) {
??p->~Foo();
??operator?delete(p);
}

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

?

Placement?new

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

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

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.
????//
一個(gè)正常的分配。向操作系統(tǒng)請(qǐ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ū)來保存一個(gè)整型,請(qǐng)注意它的地址
????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)很簡(jiǎn)單。而有趣的問題是,為什么我需要用這種東西?以下顯示了placement?new在一些場(chǎng)景確實(shí)很有用:

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

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

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

?

刪除用placement?new?分配的對(duì)象

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

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

????delete?iptr2;???????//?Whoops,?segmentation?fault!?
嗚啊,段錯(cuò)誤啦!

????return?0;
}

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

1.??First,?the?destructor?of?the?object?thats?being?deleted?is?called.

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

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

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

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

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

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

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

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

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

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

怎么析構(gòu)呢?對(duì)于一個(gè)int,并不真的需要一個(gè)析構(gòu)函數(shù),但假設(shè)代碼是這樣的:

char?mem[sizeof(Foo)];
Foo*?fooptr?=?new?(mem)?Foo;

對(duì)于某個(gè)有意義的類Foo。我們一旦不需要fooptr了,應(yīng)該怎么析構(gòu)它呢?我們必須顯式調(diào)用它的析構(gòu)函數(shù):

fooptr->~Foo();

對(duì),顯式調(diào)用析構(gòu)函數(shù)在C++中是合法的,并且這也是唯一一種正確的做法[5]

?

結(jié)論

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

?

資源

·?????????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.

?

?

[1]

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

[2]

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

[3]

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

[4]

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

[5]

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

超強(qiáng)干貨來襲 云風(fēng)專訪:近40年碼齡,通宵達(dá)旦的技術(shù)人生

總結(jié)

以上是生活随笔為你收集整理的operator new在C++中的各种写法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。