当前位置: 首页 > 编程日记 > 正文

《Effective C++》第8章 定制new和delete-读书笔记

章节回顾:

《Effective C++》第1章 让自己习惯C++-读书笔记

《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记

《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记

《Effective C++》第3章 资源管理(1)-读书笔记

《Effective C++》第3章 资源管理(2)-读书笔记

《Effective C++》第4章 设计与声明(1)-读书笔记

《Effective C++》第4章 设计与声明(2)-读书笔记

《Effective C++》第5章 实现-读书笔记

《Effective C++》第8章 定制new和delete-读书笔记


条款49:了解new-handler的行为

当operator new无法满足某一内存分配需求时,它会抛出异常,当其抛出异常以反应一个未获满足的内存需求之前,会先调用一个客户指定的错误处理函数,一个所谓的new-handler。为了指定这个“用以处理内存不足”的函数,客户必须调用set_new_handler。

namespace std
{typedef void (*new_handler)();new_handler set_new_handler(new_handler p) throw();
}

说明:

(1)set_new_handler是声明于<new>的一个标准库函数。

(2)throw()是一份异常明细,表示该函数不抛出任何异常。

(3)形参p指向operator new无法分配足够内存时该被调用的函数,返回指针指向set_new_handler被调用前正在执行(但马上就要被替换)的那个new-handler函数。

(4)set_new_handler的例子:

void OutOfMem()
{cerr << "Unable to satisfy request for memory" << endl;abort();
}
int main()
{set_new_handler(OutOfMem);int *ptr = new int[100000000L];return 0;
}

(5)当operator new无法满足内存申请时,它会不断调用new-handler函数,直到找到足够内存。

1、一个设计良好的new-handler函数必须做以下事情:

(1)让更多内存被使用。

这可能使得operator new中下一次内存分配的尝试成功。实现这一策略的一个方法是在程序启动时分配一大块内存,然后在new-handler第一次被调用时释放它供程序使用。

(2)安装另一个new-handler。

如果当前的new-handler不能做到使更多的内存可用,或许它知道有一个不同的 new-handler 可以做到。

(3)卸载new-handler。

即将空指针传给set_new_handler,当内存分配不成功时,operator new将抛出一个异常。

(4)抛出bad_alloc(或派生自bad_alloc)的异常。

这样的异常不会被operator new捕获,因此会被传播到内存请求的地方。

(5)不返回。

通常调用abort或exit。

2、以不同方式处理分配内存失败

只要让每一个class提供set_new_handler和operator new的自己的版本即可。operator new无法分配足够内存时应抛出bad_alloc异常,但也可能返回null(传统形式仍然保留),这种传统形式称为“nothrow”。

class Widget { ... };
Widget *pw1 = new Widget;            // throws bad_alloc if allocation fails
if (pw1 == 0) ...                    // this test must fail
Widget *pw2 =new (std::nothrow)  Widget;    // returns 0 if allocation for the Widget fails
if (pw2 == 0) ...                            // this test may succeed

请记住:

(1)set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。

(2)nothrow new是一个局限的工具,因为它只适用于内存分配,后继的构造函数调用还是可能抛出异常。

———————————————————————————————————————————————————————

条款50:了解new和delete的合理替换时机

为什么有些人想要替换编译器提供的operator new或operator delete 版本呢?原因有:

(1)为了检测运用错误;(2)为了收集动态分配内存之使用统计信息;(3)为了增加分配和归还的速度;(4)为了降低缺省内存管理器带来的空间额外开销;(5)为了弥补缺省分配器中的非最佳对齐;(6)为了将相关对象成簇集中;(7)为了获得非传统的行为。

———————————————————————————————————————————————————————

条款51:编写new和delete时需固守常规

1、operator new

实现一致性operator new需要考虑:

(1)必须返回正确的值,内存不足时调用new-handling函数。

(2)必须有对付0内存需求的准备。

(3)避免不慎掩盖正常形式的new。

下面分别说明:

(1)如果operator new有能力供应客户申请的内存,就返回一个指针指向那块内存,如果没有就抛出一个bad_alloc异常。

注意:只有当指向new-handling函数的指针是null,operator new才会抛出异常。

(2)即使客户要求0bytes,operator new也要返回一个合法指针,这种行为是为了简化语言其他部分。下面是个operator new伪码:

void * operator new(std::size_t size) throw(std::bad_alloc)
{                                            // your operator new mightusing namespace std;                    // take additional paramsif (size == 0) {                        // handle 0-byte requestssize = 1;                            // by treating them as 1-byte requests
    }while (true) {attempt to allocate size bytes;if (the allocation was successful)return (a pointer to the memory);// allocation was unsuccessful; find out what the// current new-handling function is (see below)new_handler globalHandler = set_new_handler(0);set_new_handler(globalHandler);if (globalHandler) (*globalHandler)();else throw std::bad_alloc();}
}

说明:

1)把0bytes申请量视为1byte申请量,毕竟客户多久才会发出一个0bytes申请。

2)将new-handling函数指针设为null而后又立即恢复原样,是因为没有办法可以直接取得new-handling函数指针,所以必须调用set_new_handler找出它来。这种做法在单线程环境下有效,多线程环境下或许需要某种锁以便安全处置new-handling函数背后的数据结构。

下面考虑operator new成员函数被继承会发生什么情况:

如果Base class专属的operator new并非被设计用来处理上述情况。最佳做法是采用标准operator new:

void * Base::operator new(std::size_t size) throw(std::bad_alloc)
{if (size != sizeof(Base))  // if size is "wrong,"return ::operator new(size) ; // have standard operator// new handle the request... // otherwise handle// the request here
}

2、operator delete

C++保证“删除null指针永远安全”,所以你必须兑现这项保证。

class Base { // same as before, but now
public: // operator delete is declaredstatic void * operator new(std::size_t size) throw(std::bad_alloc);static void operator delete(void *rawMemory, std::size_t size) throw();...
};
void Base::operator delete(void *rawMemory, std::size_t size) throw()
{if (rawMemory == 0) return; // check for null pointerif (size != sizeof(Base)) {  // if size is "wrong,"::operator delete(rawMemory);  // have standard operatorreturn;  // delete handle the request
    }deallocate the memory pointed to by rawMemory;return;
}

请记住:

(1)operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。它也应该有能力处理0bytes申请。class专属版本还应该处理“比正确大小更大的(错误)申请”。

(2)operator delete应该在收到null指针时不做任何事。class专属版本还应该处理“比正确大小更大的(错误)申请”。

———————————————————————————————————————————————————————

条款52:写了placement new也要写placement delete

当你写一个new表达式:

Widget *pw = new Widget;

共有两个函数被调用:第一个是operator new用于分配内存,第二个是Widget的default 构造函数。

假设第一个调用成功,第二个调用导致抛出一个异常。那么在第1步中完成的内存分配必须被撤销,否则内存泄漏。但客户不可能回收这些内存,因为如果Widget的构造函数抛出一个异常,pw根本就没有被赋值。对于客户来说无法得到指向应该被回收的内存指针。所以撤销第1步的职责必然落在了C++ 运行时系统身上。C++ 运行时系统会调用第1步operator new相应的operator delete,但只有在它知道哪一个operator delete(可能有许多个)该被调用。

(1)对于正常的operator new,运行时系统可以找到对应的operator delete。常规的operator new

void* operator new(std::size_t) throw(std::bad_alloc);

对应常规的operator delete:

void operator delete(void *rawMemory) throw();                        // normal signature at global scope
void operator delete(void *rawMemory, std::size_t size) throw();    // typical normal signature at class scope 

(2)当声明operator new的非常规形式(带有额外参数)的时候,问题就出现了。假设编写了一个类专用的operator new,但编写了一个常规的operator delete:

class Widget {
public:...static void* operator new(std::size_t size, // non-normalstd::ostream& logStream)                    // form of newthrow(std::bad_alloc);static void operator delete(void *pMemory    // normal class-std::size_t size) throw();                // specific form// of delete
    ...
};

说明:如果operator new接受的参数除了size_t之外还有其他,称为placement new。比较有用的一个placement new版本是“接受一个指针指向对象该被构造之处”。

void* operator new(std::size_t, void *pMemory) throw();

考虑以下代码:

Widget *pw = new (std::cerr) Widget; // call operator new, passing cerr as// the ostream; this leaks memory// if the Widget constructor throws

如果内存分配成功,构造函数抛出异常。运行时系统寻找“参数个数和类型都与operator new相同”的某个operator delete,如果找到则调用。显然,这里没有找到,所以运行时系统无法取消分配的内存。对应于placement new的placement delete版本类似于:

void operator delete(void *, std::ostream&) throw();

然而如果构造函数没有抛出异常,客户代码为delete pw,调用的是正常形式的operator delete,而非placement版本。

注意:只有在调用一个与placement new相关联的构造函数抛出异常,placement delete才会被调用。所以如果要处理所有与placement new相关的内存泄露,必须同时提供正常的operator delete和placement版本。

另外需要注意的是,成员函数名称会掩盖其外围作用域中的相同名称,所以要避免让class专属的news遮盖客户期望的其他news(包括正常版本)。

class Base {
public:...static void* operator new(std::size_t size, // this new hides the normal global formsstd::ostream& logStream) throw(std::bad_alloc); ...
};
Base *pb = new Base;                // error! the normal form of// operator new is hidden
Base *pb = new (std::cerr) Base;    // fine, calls Base's// placement new

同样道理,derived classes中的operator news会遮盖globle版本和继承而得到的operator new。

class Derived: public Base {                        // inherits from Base above
public:...static void* operator new(std::size_t size) // redeclares the normalthrow(std::bad_alloc);                        // form of new
    ...
};
Derived *pd = new (std::clog) Derived;    // error! Base's placement// new is hidden
Derived *pd = new Derived;                // fine, calls Derived's// operator new

对于撰写内存分配函数,需要注意的是,缺省情况下C++在globle作用域内提供的operator news形式:

void* operator new(std::size_t) throw(std::bad_alloc); // normal new
void* operator new(std::size_t, void*) throw(); // placement new
void* operator new(std::size_t,                // nothrow new — see Item 49const std::nothrow_t&) throw();            

如果你在class内声明任何operator news都会遮掩上述标准形式,除非你就是故意阻止客户使用这些形式。如果你希望这些函数可用,只要令你的class专属版本调用globle版本即可。

class StandardNewDeleteForms {
public:// normal new/deletestatic void* operator new(std::size_t size) throw(std::bad_alloc){ return ::operator new(size); }static void operator delete(void *pMemory) throw(){ ::operator delete(pMemory); }// placement new/deletestatic void* operator new(std::size_t size, void *ptr) throw(){ return ::operator new(size, ptr); }static void operator delete(void *pMemory, void *ptr) throw(){ return ::operator delete(pMemory, ptr); }// nothrow new/deletestatic void* operator new(std::size_t size, const std::nothrow_t& nt) throw(){ return ::operator new(size, nt); }static void operator delete(void *pMemory, const std::nothrow_t&) throw(){ ::operator delete(pMemory); }
};class Widget: public StandardNewDeleteForms {            // inherit std forms
public:using StandardNewDeleteForms::operator new;            // make thoseusing StandardNewDeleteForms::operator delete;        // forms visiblestatic void* operator new(std::size_t size,             // add a customstd::ostream& logStream)                        // placement newthrow(std::bad_alloc);static void operator delete(void *pMemory,            // add the corresponding placement deletestd::ostream& logStream)                        throw(); ...
};

请记住:

(1)当你写operator new的placement版本时,确保同时编写operator delete相应的placement版本。否则,你的程序可能会发生微妙的,断续的内存泄漏。

(2)当你声明new和delete的placement版本时,确保不会无意中覆盖这些函数的常规版本。

———————————————————————————————————————————————————————

我感觉写的有点流水账。但其实每次拜读这本书感受都有点不同,比如有的问题,自己知道这样做肯定行,但是就是想不明白为什么这么麻烦着做。可能是没有经验吧。这篇总结,先记录到这里,我还要回顾修改的。看起来有点乱糟糟的。

转载于:https://www.cnblogs.com/mengwang024/p/4433131.html

相关文章:

观点:AI 与自动化是矛盾的

作者&#xff1a;cerebralab.com译者&#xff1a;张雨佳原文标题&#xff1a;AI and automation are at odds想象一下&#xff0c;我们生活在一个完美、和谐的地方&#xff0c;所有人在某一天同意让电脑代替人类驾驶汽车&#xff0c;而不是像现在逐步推进自动驾驶。那么&#x…

kaggle之数据分析从业者用户画像分析

数据为kaggle社区发布的数据分析从业者问卷调查分析报告&#xff0c;其中涵盖了关于该行业不同维度的问题及调查结果。本文的目的为提取有用的数据&#xff0c;进行描述性展示。帮助新从业的人员更全方位地了解这个行业。 参考学习视频:http://www.tianshansoft.com/ 数据集&am…

mysql读写分离(MySQL Proxy 安装和使用)

一、必备软件&#xff1a; 1、LUA 可以去LUA的官方下载&#xff1a;www.lua.org 2、MySQL Proxy 这里有好多二进制版本。 http://mysql.cdpa.nsysu.edu.tw/Downloads/MySQL-Proxy/ 或者去MYSQL官方下载源代码。 3、测试过程中取消了B和C的REPLICATION。这样SQL语句…

微软副总裁、Kubernetes 头号贡献者的他,给云原生带来奇迹!

我们做了一个街头采访&#xff0c;调查路人眼中的程序员到底是怎样的&#xff1f;提到程序员&#xff0c;大家似乎都有刻板印象&#xff1a;总是格子衬衫牛仔裤双肩包打扮&#xff0c;总是埋头敲代码&#xff0c;加班是常态……谁说程序员呆板木讷&#xff0c;只会埋头敲一行行…

查询Oracle中字段名带.的数据

SDE中的TT_L线层会有SHAPE.LEN这样的字段&#xff0c;使用&#xff1a; SQL>select shape.len from tt_l; 或 SQL>select t.shape.len from tt_l t; 是查询不出来的。 需要这样查询&#xff1a; SQL>select t."SHAPE"."LEN" from tt_l t; 转载于:…

再谈session共享

之前一篇已经写过了《springboot中redis的使用和分布式session共享问题》&#xff0c;但是示例不完全&#xff0c;本文加以完善。 使用spring-session-data-redis解决session共享&#xff0c;而不需要再引入其他jar即可 集成简单&#xff0c;上手迅速。 项目结构 1.pngpom <…

使用MySQL Proxy解决MySQL主从同步延迟

MySQL的主从同步机制非常方便的解决了高并发读的应用需求&#xff0c;给Web方 面开发带来了极大的便利。但这种方式有个比较大的缺陷在于MySQL的同步机制是依赖Slave主动向Master发请求来获取数据的&#xff0c;而且由于服务器负 载、网络拥堵等方面的原因&#xff0c;Master与…

Python 操作 MongoDB 数据库!

作者 |黄伟呢来源 |数据分析与统计学之美MongoDB是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰富&#xff0c;最像关系数据库的。先来看看MySQL与MongoDB 概念区别&#xff1a;今天的重点&#xff0c;就是要为大家讲述如何使用Python操…

Linux下用汇编输出Hello, world

下列是Intel汇编语法实现的 Hello, world!程序。 ;; hello.asm ;; nasm -f elf hello.asm; will output hello.o ;; ld -s -o hello hello.o;; section, same to segment segment .data ; 数据段声明, 下列代码将放在数据段中msg db "Hello, world!", 0xA ; 要…

利用bigpipe机制实现页面模块的异步渲染 chunked技术

bigpipe基于HTTP/1.1 支持的chunked编码&#xff0c;可以由浏览器接收到服务器发送的chunked块后&#xff0c;立即解析该块代码。因为chunked编码使消息主体成块发送&#xff0c;每块有自己的大小指示器&#xff0c;在所有的块之后会紧接着一个可选的包含实体头域的尾部。这种编…

hibernate 全面学习【lazy策略 】

2019独角兽企业重金招聘Python工程师标准>>> lazy策略可以用在&#xff1a; * <class>标签上&#xff1a;可以取值true/false * <property>标签上&#xff0c;可以取值true/false&#xff0c;这个特性需要类增强 * <set>/<list>等集合上…

深度学习发现古人类遗址,AI 考古比胡八一更高效

作者 |神经星星来源 |HyperAI超神经By 超神经内容一览&#xff1a;伊利诺伊州立大学人类学专业考古方向的研究人员&#xff0c;将空间遥感技术和深度学习应用于古人类遗址的发掘和研究。关键词&#xff1a;考古 遥感 机器视觉考古&#xff0c;一直是个神秘又充满吸引力的话题。…

linux resource

1. centos repo https://centos.pkgs.org/转载于:https://www.cnblogs.com/gojoin/p/8241068.html

MySQL 水平分区方案Spock Proxy

Spock Proxy 是由实际项目产生的一个开源项目&#xff08;Spock是Rails的应用&#xff0c;Speck Proxy应当可用于Rails之外的&#xff0c;例如PHP或.NET&#xff09;&#xff0c;基于MySQL Proxy开发&#xff0c;是MySQL Proxy的一个分支&#xff0c;支持range-based horizonta…

卷学历、卷加班……程序员拥有什么能力才能破局内卷?

全世界的天才程序员都在疯狂的造轮子&#xff0c;其它程序员只能被动的学习轮子&#xff0c;这简直就像一场绝地求生。程序员行业的内卷已是有目共睹&#xff0c;选择程序员作为职业的人越来越多&#xff1b;大厂对程序员的学历要求越来越高&#xff1b;程序员工作加班越来越严…

LeetCode - Maximum Depth of Binary Tree

递归求二叉树的最大深度。 /*** Definition for binary tree* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val x; }* }*/ public class Solution {public int maxDepth(TreeNode root) {if(root null)return 0…

2018年IT市场最大的技术趋势和热点预测

随着数字化&#xff0c;物联网&#xff08;IoT&#xff09;设备&#xff0c;区块链应用以及新的服务器战略日益普及&#xff0c;这可能会成为2018年最大的技术趋势。我们期待看到大量与边缘计算相关的新产品&#xff0c;即所谓的无服务器计算&#xff0c;智能家居&#xff0c;智…

哈希分布与一致性哈希算法简介

前言 在我们的日常web应用开发当中memcached可以算作是当今的标准开发配置了。相信memcache的基本原理大家也都了解过了&#xff0c;memcache虽然是分布式的应用服务&#xff0c;但分布的原则是由client端的api来决定的&#xff0c;api根据存储用的key以及已知的服务器列表&…

使用深度学习阅读和分类扫描文档

作者|小白来源|小白学视觉收集数据首先&#xff0c;我们要做的第一件事是创建一个简单的数据集&#xff0c;这样我们就可以测试我们工作流程的每一部分。理想情况下&#xff0c;我们的数据集将包含各种易读性和时间段的扫描文档&#xff0c;以及每个文档所属的高级主题。我找不…

无聊的时候,冷死了(六)

阁下长得真是天生励志&#xff01;好久没有听到有人能把牛吹得这么清新脱俗了&#xff01;你出生时就丑的躲起来了&#xff0c;连你父母都不敢见你&#xff0c;你还怕有人举报你&#xff1f;你拉着一头猪逛街&#xff0c;很幸福的样子&#xff0c;我经过满怀同情的说&#xff1…

Java EE 开发环境搭建

下载安装Java EE SDK 版本&#xff1a;Java Platform,Enterprise Edition 7 SDK (with JDK 7u45) 下载页面&#xff1a; http://www.oracle.com/technetwork/java/javaee/downloads/java-ee-7-sdk-with-jdk-u45-2066865.html 文件名&#xff1a;java_ee_sdk-7-jdk7-windows.exe…

memcacheq 服务安装与原理

memcacheQ是一个单纯的分布式消息队列服务。它的安装依赖于BerkeleyDB 和 libevent&#xff0c;所以要先安装这BerkeleyDB和libevent&#xff1a; 一&#xff0c;BerkeleyDB 下载软件包&#xff0c;http://download.oracle.com/berkeley-db/db-5.0.21.tar.gz解压缩后&#xff…

AI 帮忙找 Bug ,英特尔开源代码编程工具 ControlFlag

整理 | 孙胜出品 | CSDN近日&#xff0c;英特尔开源了自动代码调试工具 ControlFlag 源代码&#xff0c;ControlFlag 源码现在可通过 GitHub 获得。据了解&#xff0c;ControlFlag 可用来帮助更多开发者自主检测代码错误&#xff0c;主要利用 AI 自动识别软件和固件代码中的错误…

一次心惊肉跳的服务器误删文件的恢复过程

经历了两天不懈努力&#xff0c;终于恢复了一次误操作删除的生产服务器数据。对本次事故过程和解决办法记录在此&#xff0c;警醒自己&#xff0c;也提示别人莫犯此错。也希望遇到问题的朋友能找到一丝灵感解决问题。事故背景安排一个妹子在一台生产服务器上安装Oracle,妹子边研…

【vue】vue中ref用法

1.获取当前元素&#xff1a; 例子&#xff1a; <div class"pop pos-a" :style"{ left: pop_x px ,top: pop_y px}" ref"refName"><ul><li>编辑部门</li><li click"append()">添加子部门</li>&…

使用Gearman做分布式计算

通常&#xff0c;多语言多系统之间的集成是个大问题&#xff0c;一般来说&#xff0c;人们多半会采用WebService的方式来处理此类集成问题&#xff0c;但不管采用何种风格的WebService&#xff0c;如RPC风格&#xff0c;或者REST风格&#xff0c;其本身都有一定的复杂性。相比之…

把数据库中有关枚举项值的数字字符串转换成文字字符串

原文:把数据库中有关枚举项值的数字字符串转换成文字字符串标题可能无法表达我的本意。比如&#xff0c;有这样一个枚举&#xff1a; public enum MyChoice{MyFirstChoice 0,MySecondChoice 1,MyThirdChoice 2} 数据库中&#xff0c;某表某字段保存值为"0,1,2"&…

又被 AI 抢饭碗?2457 亿参数规模,全球最大中文人工智能巨量模型 “源1.0”正式开源...

作者 | 伍杏玲 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;输入&#xff1a;昔我往矣&#xff0c;杨柳依依。今我来思&#xff0c;雨雪霏霏。行道迟迟&#xff0c;载渴载饥。我心伤悲&#xff0c;莫知我哀&#xff01;&#xff08;以战争为题写一首诗&#xff09…

Java架构演进之路

2019独角兽企业重金招聘Python工程师标准>>> hello 转载于:https://my.oschina.net/mrpei123/blog/1605391

F5与NetScaler比较

F5 是基于Linux的&#xff0c;NetScaler 是基于BSD的。F5 的四层走的是硬件芯片&#xff0c;七层走的是软件&#xff0c;NetScaler 全部走的是软件。我测试的性能也是 F5比NetScaler强&#xff0c;在均不使用压缩的情况下&#xff0c;NetScaler比F5消耗更大的带宽。