條款8:了解各種不同意義的new和delete

有時候我們覺得,C++的術語仿佛是要故意讓人難以理解似的。

這里就有一個例子:請說明new operator 和operator new 之間的差異(譯注:本書所說的new operator,即某些C++教程如C++ Primer 所謂的new expression)

當你寫出這樣的代碼:

string *ps= new string("Memory Management");

你所使用的 new 是所謂的new operator。

這個操作符是由語言內建的,就像sizeof那樣,不能被改變意義,總是做相同的事情。

它的動作分為兩方面。

  1. 第一,它分配足夠的內存,用來放督某類型的對象。以上例而言,它分配足夠放置一個string 對象的內存。
  2. 第二,它調用一個constructor,為剛才分配的內存中的那個對象設定初值。

new operator總是做這兩件事,無論如何你不能夠改變其行為。

你能夠改變的是用來容納對象的那塊內存的分配行為。也就是上面的第一步

new operator 調用某個函數,執行必要的內存分配動作你可以重寫或重載那個函數,改變其行為。這個函數的名稱叫做operator new。頭昏了嗎?真的,我說的是真的。

函數operator new 通常聲明如下:

void * operator new(size_t size);

其返回值類型是void*。此函數返回一個指針,指向一塊原始的、未設初值的內存(如果你喜歡,可以寫一個新版的operator new,在其返回內存指針之前先將那塊內存設定初值。只不過這種行為頗為罕見就是了)。

函數中的size_t 參數表示需要分配多少內存。

你可以將operator new 重載,加上額外的參數,但第一參數的類型必須總是 size_t(如何撰寫 operator new,相關信息請參考條款E8~E10)

吸中協從不想到要直接調用operator new,但如果你要,你可以像調用任何其他函數一樣地調用它:

void *rawMemory = operator new(sizeof(string));

這里的operator new將返回指針,指向一塊足夠容納一個string對象的內存。

和malloc一樣,operator new的唯一任務就是分配內存。它不知道什么是constructors, operator new只負責內存分配。

取得 operator new 返回的內存并將之轉換為一個對象,是new operator 的責任。

當你的編譯器看到這樣一個句子:

string *ps= new string("Memory Management");

它必須產生一些代碼,或多或少會反映以下行為(見條款E8和條款E10,以及發表于C/C++ Users Journal, April1998 的文章《Counting Objects inC++》中的方塊內容)

void *memory?=
operator new(sizeof(string));//取得原始內存(raw memory)。用來放置一個string對象。call string::string("Memory Management")//將內存中的對象初始化。
on *memorystring *ps=
static_cast<string*>(memory);//讓ps指向新完成的對象。

注意上述第二步驟涉及“調用一個constructor”,身為程序員的你沒有權力這么做。

然而你的編譯器百無禁忌,可以為所欲為。這就是為什么如果你想要做出一個heap-based object,一定得使用new operator 的原因:你無法直接調用“對象初始化所必需的constructor”
(尤其它可能得為重要成分vtbl設定初值,見條款24)。

Placement new

有時候你真的會想直接調用一個constructor。

針對一個已存在的對象調用其constructor 并無意義,因為 constructors 用來將對象初始化,而對象只能被初始化一次。

但是偶爾你會有一些分配好的原始內存,你需要在上面構建對象。有一個特殊版本的operator new,稱為placement new,允許你那么做。
下面示范如何使用 placement new:

class Widget {
public:
widget(int widgetsize);
//...
}widget * constructWidgetInBuffer (void *buffer, int widgetSize)
{
return new (buffer) Widget(widgetsize);
}

此函數返回指針,指向一個widget object,它被構造于傳遞給此函數的一塊內存緩沖區上。

當程序運行到shared memory 或memory-mapped I/O,這類函數可能是有用的,因為在那樣的運用中,對象必須置于特定地址,或是置于以特殊函數分配出來的內存上(條款4列有placement new的另一個運用實例)

在 constructWidgetInBuffer 函數內部,唯一一個表達式是,

new (buffer) Widget(widgetSize)

乍見之下有點奇怪,其實不足為奇,這只是new operator 的用法之一,其中指定一個額外自變量(buffer)作為 new operator “隱式調用operator new”時所用。

于是,被調用的operator new除了接受“一定得有的size_t 自變量”之外,還接受了一個void*參數,指向一塊內存,準備用來接受構造好的對象。這樣的operator new 就是所謂的placement new,看起來像這樣:

void * operator new (size_t,void *location)//注意size_t后面沒名字
{
return location;
}

似乎比你預期得更簡單,但這便是placement new必須做的一切。

畢竟operator new的目的是要為對象找到一塊內存,然后返回一個指針指向它。

在placement new的情況下,調用者已經知道指向內存的指針了,因為調用者知道對象應該放在哪里。因此placement new 唯一需要做的就是將它獲得的指針再返回。

至于沒有用到(但一定得有)的size_t參數,之所以不賦予名稱,為的是避免編譯器發出“某物未被使用”的警告(見條款6)Placement new 是C++標準程序庫(見條款E49)的一部分。

欲使用 placement new,你必須用#include <new>。如果你的編譯器尚未支持新式頭文件名稱的話(見條款E49),就用#include<new.h>。

花幾分鐘回頭想想 placement new,我們便能了解 new operator 和 operator new之間的關系,兩個術語雖然表面上令人迷惑,概念上卻十分直接易懂。

  1. 如果你希望將對象產生于heap,請使用 new operator。它不但分配內存而且為該對象調用一個constructor。
  2. 如果你只是打算分配內存,請調用 operator new,那就沒有任何constructor會被調用。
  3. 如果你打算在heap objects 產生時自己決定內存分配方式,請寫一個自己的 operator new,并使用 new operator,它將會自動調用你所寫的operator new。
  4. 如果你打算在已分配(并擁有指針)的內存中構造對象,請使用placement new(若想更深入地了解new和delete,請見條款E7及發表于C/C++Users Journal, April1998的文章《Counting Objects in C++》)

刪除(Deletion)與內存釋放(Deallocation)

為了避免resource leaks(資源泄漏),每一個動態分配行為都必須匹配一個相應但相反的釋放動作。函數operator delete 對于內建的delete operator,就好像operator new對于new operator 一樣。當你寫出這樣的代碼:

string *ps;
//。。。
delete ps;// 使用 delete operator。


你的編譯器必須產生怎樣的代碼?

它必須既能夠析構 ps所指對象,又能夠釋放被該對象占用的內存。

內存釋放動作是由函數operator delete 執行,通常聲明如下:

void operator delete(void *memoryToBeDeallocated);

因此,下面這個動作:

delete ps;


會造成編譯器產生近似這樣的代碼:

ps->~string();// 調用對象的dtoroperator。operator delete (ps);//釋放對象所占用的內存。

這里呈現的一個暗示就是,如果你只打算處理原始的、未設初值的內存,應該完全回避new operator 和 delete operators,改調用operator new取得內存并以operator delete 歸還給系統:

void *buffer=operator new(50*sizeof(char));//分配足夠的內存,放置50個 chars;沒有調用任何ctorsoperator delete(buffer);//釋放內存,沒有調用任何dtors。

這組行為在C++中相當于調用malloc和free。

如果你使用placement new,在某內存塊中產生對象,你應該避免對那塊內存使用delete opcrator.因為 delete operator會調用 operator delete來釋放內存,但是該內存內含的對象最初并非是由 operator new分配得來的。

畢竟placemen new只是返回它所接收的指針而已,誰知道那個指針從哪里來呢?所以為了抵消該對象的constructor的影響,你應該直接調用該對象的destructor:

//以下函數用來分配及釋放 shared memory 中的內存。
void* mallocshared(size_t size);
void freeShared(void *memory);void*sharedMemory=mallocShared(sizeof(Widget));//和先前相同,運用Widget*pw=constructWidgetInBuffer(sharedMemory, 10); // placement new。
//..
delete pw;//無定義!因為sharedMemory 來自mallocshared,不是來自 operator new。pw->~Widget();
//可!析構 pw 所指的widget 對象,
//但并未釋放widget占用的內存。freeShared(pw);
//可!釋放 pw所指的內存,
//不調用任何 destructor。

如此例所示,如果交給placement new 的原始內存(raw memory)本身是動態分配而得(通過某種非傳統做法),那么你最終還是得釋放那塊內存,以免遭受內存泄漏(memory leak)之苦(請參考文章《Counting Objects inC++》之中的方塊內容,其中對所謂的“placement delete”有些介紹)。

數組(Arrays)

目前為止一切都好,但我們還有更遠的路要走。截至目前我們考慮的每件事情都只在單一對象身上打轉。面對數組怎么辦?下面會發生什么事情;

string *ps= new string[10);//分配一個對象數組

上述使用的new 仍然是那個new operator,但由于誕生的是數組,所以new operator的行為與先前產生單一對象的情況略有不同。

是的,內存不再以operator new分配,而是由其“數組版”兄弟,一個名為operator new[]的函數負責分配(通常被稱為“aray new”)。

和operator new一樣,operator new[]也可以被重載。這使你得奪取數組的內存分配權,就像你可以控制單一對象的內存分配一樣(不過條款E8對此有些警告)。

operatornew[]是相當晚的時候才加入C++的一個特性,所以你的編譯器或許尚未支持它。

如果是這樣,全局operator new會被用來為每個數組分配內存一不論數組中的對象類型是什么。

在這樣的編譯器下定制“數組內存分配行為”很困難,因為你得改寫全局版的 operator new才行。這可不是件容易的工作。

默認情況下全局版的operator new負責程序中所有的動態內存分配,所以其行為的任何改變都可能帶來劇烈而普遍的影響。此外,全局版本的operator new,其正規形式的型構(eignature)(我的意思是,只有唯一size_t參數的那個,見條款E9)只有一個,所以如果你決定聲稱它為你所擁有,你的軟件便立刻不容于任何做了相同決定的程序庫(見條款 27)。

多方考慮之下,如果你面對的是尚未支持 。perator new[]的編譯器,定制“數組內存管理行為”往往不是個理想的決定。

“數組版”與“單一對象版”的newoperator的第二個不同是,它所調用的constructor數量。數組版new operator 必須針對數組中的每個對象調用一個constructor:

string *ps =//調用operator new[]以分配足夠容納new string[10];//10個 string 對象的內存,然后
//針對每個元素調用 string default ctor。

同樣道理,當delete operator 被用于數組,它會針對數組中的每個元素調用其 destructor,然后再調用 operator delete[]釋放內存:

delete []?ps;
//為數組中的每個元素調用 string dtor.

然后調用 operator delete[]以釋放內存。

就好像你可以取代或重載 operator delete一樣,你也可以取代或重載operator delete[]。不過兩者的重載有著相同的限制。請你找一本好的C++教程,查閱其細節。說到好的C++教程,本書p285列有我的一份推薦名單。

現在,你有了完整的知識。

new operator 和delete operator 都是內建操作符,無法為你所控制,但是它們所調用的內存分配/釋放函數則不然。

當你想要定制new operator 和 delete operator的行為,記住,你其實無法真正辦到。你可以修改它們完成任務的方式,至于它們的任務,已經被語言規范固定死了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/16877.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/16877.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/16877.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

粒子愛心特效||輕松實現浪漫效果||完整代碼

關注微信公眾號「ClassmateJie」有完整代碼以及更多驚喜等待你的發現。 簡介/效果展示 你是否曾經想過&#xff0c;在特殊的日子里給你的愛人一個驚喜&#xff1f;或者在朋友的生日派對上&#xff0c;給他們展示一個充滿愛意的特效&#xff1f;今天&#xff0c;我要分享一個我…

VUE3-form表單保存附件與基本信息

element-ui代碼 <el-dialog :title"上傳附件" v-model"dialogAdds.visible" width"500px" append-to-body> <el-form-item label"唯一標識"> <dict-tag v-if"form.groupId" :options"unique_identifica…

[大師C語言(第十二篇)]C語言堆排序技術詳解

引言 堆排序&#xff08;Heap Sort&#xff09;是一種基于比較的排序算法&#xff0c;它利用堆這種數據結構的特點來進行排序。堆是一種近似完全二叉樹的結構&#xff0c;并同時滿足堆積的性質&#xff1a;即子節點的鍵值或索引總是小于&#xff08;或者大于&#xff09;它的父…

性能怪獸!香橙派 Kunpeng Pro 開發板深度測評,帶你解鎖無限可能

性能怪獸&#xff01;香橙派 Kunpeng Pro 開發板深度測評&#xff0c;帶你解鎖無限可能 文章目錄 性能怪獸&#xff01;香橙派 Kunpeng Pro 開發板深度測評&#xff0c;帶你解鎖無限可能一、背景二、香橙派 Kunpeng Pro 硬件規格概述三、使用準備與系統安裝1??、系統安裝步驟…

【C++】淺論(cin和cout)的解鎖、緩沖區的理解、CC++輸入方法匯總和詳解

一、cin,cout解鎖 1.1&#xff1a;cin,cout解鎖以及why 首先cin和cout是在c中為了提供類型安全和易用性設計的&#xff0c;它兼容了c語言的輸入和輸出&#xff0c;以上幾點導致它在性能行&#xff08;讀取和輸出速度)遠不如傳統c語言的輸入和輸出。 在看到一些代碼里面&…

Python 腳本化 Git 操作:簡單、高效、無壓力

前言 如何判定此次測試是否達標&#xff0c;代碼覆蓋率是衡量的標準之一。前段時間&#xff0c;利用fastapi框架重寫了覆蓋率統計服務&#xff0c;核心其實就是先獲取全量代碼覆蓋率&#xff0c;然后通過diff操作統計增量代碼覆蓋率&#xff0c;當然要使用diff操作&#xff0c…

Java中Stack的使用詳解

Stack是一種運算受限的線性表&#xff0c;其特點在于僅允許在表的一端&#xff08;即表尾&#xff09;進行插入和刪除操作。這一端被稱為棧頂&#xff0c;而相對的另一端則稱為棧底。向一個棧插入新元素的操作稱為進棧或入棧&#xff0c;它將新元素放到棧頂元素的上面&#xff…

從雜亂無章到井井有條——五款筆記軟件,重塑工作與生活

記得剛入職場那會&#xff0c;我總是被各種繁雜的信息和任務搞得焦頭爛額。會議記錄、項目計劃、靈感閃現……這些都需要我隨時記錄和整理。 然而&#xff0c;我的桌面總是堆滿了便簽紙和草稿本&#xff0c;手機相冊里充斥著各種截圖和備忘錄&#xff0c;每次需要查找資料都像…

【數據結構】紅黑樹——領略天才的想法

個人主頁&#xff1a;東洛的克萊斯韋克-CSDN博客 祝福語&#xff1a;愿你擁抱自由的風 目錄 二叉搜索樹 AVL樹 紅黑樹概述 性質詳解 效率對比 旋轉操作 元素操作 代碼實現 二叉搜索樹 【數據結構】二叉搜索樹-CSDN博客 AVL樹 【數據結構】AVL樹——平衡二叉搜索…

深度學習實戰-yolox訓練ExDark數據集(附全過程代碼,超詳細教程,無坑!)

跳轉:數據集獲取以及前期準備工作 本人在深度學習實戰-yolov5訓練ExDark數據集(附全過程代碼,超詳細教程,無坑!)的數據基礎上實現yolox的訓練,所以先跳轉到該文章下去獲取數據集,再繼續接下來操作過程。 一、VOC格式數據集制作 1.前期工作 2.轉變成voc格式 在datase…

Latex:newcommand

參考文獻&#xff1a; latex中自定義的命令———\newcommand-CSDN博客LaTeX技巧924&#xff1a;詳解newcommand的參數和默認值 - LaTeX工作室 (latexstudio.net) 文章目錄 (re)newcommand自定義的一些命令 (re)newcommand ”定義命令“ 的定義&#xff1a; \newcommand{<…

[每周一更]-(第98期):PHP版本的升級歷程

文章目錄 大致歷程PHP/FI (PHP 1.0)PHP 2.0PHP 3.0PHP 4.0PHP 5.0PHP 5.3 - 5.6PHP 7.0PHP 7.1 - 7.4PHP 8.0PHP 8.1 - 8.2 參考 PHP&#xff0c;即“超文本預處理器”&#xff08;Hypertext Preprocessor&#xff09;&#xff0c;是廣泛應用于web開發的服務器端腳本語言。自19…

什么是獨特擺動交易策略?fpmarkets1分鐘講清楚

擺動交易策略想必各位投資者都已經接觸過了&#xff0c;但是什么是獨特擺動交易策略&#xff1f;各位投資者知道嗎&#xff1f;其實很簡單&#xff0c;這是一種基于斐波納契工具的獨特擺動交易策略。下面fpmarkets1分鐘講清楚&#xff0c;趨勢總會經歷調整&#xff0c;而這些調…

【機器學習】Python中的決策樹算法探索

&#x1f308;個人主頁: 鑫寶Code &#x1f525;熱門專欄: 閑話雜談&#xff5c; 炫酷HTML | JavaScript基礎 ?&#x1f4ab;個人格言: "如無必要&#xff0c;勿增實體" 文章目錄 Python中的決策樹算法探索引言1. 決策樹基礎理論1.1 算法概述1.2 構建過程 2. P…

數據集003:貓類識別-12種貓分類數據集 (含數據集下載鏈接)

數據集簡介&#xff1a; 訓練集共有2160張貓的圖片, 分為12類. train_list.txt是其標注文件 測試集共有240張貓的圖片. 不含標注信息. 訓練集圖像&#xff08;部分&#xff09; 驗證集圖像&#xff08;部分&#xff09; 標簽 部分代碼&#xff1a; # 定義訓練數據集 class T…

eNSP華為模擬器-DHCP配置

拓撲圖 要求 PC1通過DHCP獲取192.168.1.1地址PC2和PC3通過DHCP接口地址池方式獲取IP地址配置靜態路由使其ping通 配置 配置主機名及接口IP地址 # AR1 <Huawei>sys Enter system view, return user view with CtrlZ. [Huawei]sys AR1 [AR1]int g0/0/0 [AR1-Gigabit…

在leaflet上創建圖標

參考之前博客進行創建leaflet地圖 添加圖標 customIcon L.icon({iconUrl: helicopter0.png,//圖片路徑放在public中iconSize: [35, 35],iconAnchor: [15, 15],tooltipAnchor: [20, 0],}); let marker L.marker([obj.lat, obj.lon], { icon: customIcon, rotationAngle: 偏轉…

去重復記錄和排序——kettle開發09

一、去除重復記錄 去除重復記錄&#xff0c;就是將數據流中的數據進行字段比較&#xff0c;從而去掉重復值的過程。去除重復記錄的前提是需要將數據流中的數據進行排序&#xff0c;然后再進行去重操作。 去除重復記錄的邏輯是&#xff0c;如下圖&#xff0c;我們將需要比較的…

MySQL + MyBatis-Plus 分頁數據重復問題

參考文章&#xff1a;java - MySQL MyBatis-Plus 分頁數據重復問題 - 個人文章 - SegmentFault 思否

基礎使用-SQL-圖形化界面工具DataGrip

一、連接mysql &#xff08;1&#xff09;選擇加號&#xff0c;再選擇添加一個數據源&#xff08;Data Source&#xff09;&#xff0c;然后選擇MySQL &#xff08;2&#xff09;接下來就需要去配置MySQL的連接信息&#xff0c;并且去下載它的驅動&#xff0c;安裝驅動時可能要…