《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++》第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 ;
}
(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 == ) ... // this test must fail
Widget *pw2 =new (std::nothrow) Widget; // returns 0 if allocation for the Widget fails
if (pw2 == ) ... // 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 might
using namespace std; // take additional params
if (size == ) { // handle 0-byte requests
size = ; // 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();
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 declared
static 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 == ) return; // check for null pointer
if (size != sizeof(Base)) { // if size is "wrong,"
::operator delete(rawMemory); // have standard operator
return; // 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-normal
std::ostream& logStream) // form of new
throw(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 forms
std::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 normal
throw(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 49
const std::nothrow_t&) throw();
如果你在class内声明任何operator news都会遮掩上述标准形式,除非你就是故意阻止客户使用这些形式。如果你希望这些函数可用,只要令你的class专属版本调用globle版本即可。
class StandardNewDeleteForms {
public:
// normal new/delete
static 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/delete
static 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/delete
static 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 those
using StandardNewDeleteForms::operator delete; // forms visible
static void* operator new(std::size_t size, // add a custom
std::ostream& logStream) // placement new
throw(std::bad_alloc);
static void operator delete(void *pMemory, // add the corresponding placement delete
std::ostream& logStream)
throw();
...
};
请记住:
(1)当你写operator new的placement版本时,确保同时编写operator delete相应的placement版本。否则,你的程序可能会发生微妙的,断续的内存泄漏。
(2)当你声明new和delete的placement版本时,确保不会无意中覆盖这些函数的常规版本。
———————————————————————————————————————————————————————
我感觉写的有点流水账。但其实每次拜读这本书感受都有点不同,比如有的问题,自己知道这样做肯定行,但是就是想不明白为什么这么麻烦着做。可能是没有经验吧。这篇总结,先记录到这里,我还要回顾修改的。看起来有点乱糟糟的。
《Effective C++》第8章 定制new和delete-读书笔记的更多相关文章
- effective java 第2章-创建和销毁对象 读书笔记
背景 去年就把这本javaer必读书--effective java中文版第二版 读完了,第一遍感觉比较肤浅,今年打算开始第二遍,顺便做一下笔记,后续会持续更新. 1.考虑用静态工厂方法替代构造器 优 ...
- 《Effective C++》第3章 资源管理(2)-读书笔记
章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...
- 《Effective C++》第3章 资源管理(1)-读书笔记
章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...
- 《Effective C++》第1章 让自己习惯C++-读书笔记
章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...
- 《TCP/IP详解卷1:协议》第4章 ARP:地址解析协议-读书笔记
章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...
- 《TCP/IP详解卷1:协议》第19章 TCP的交互数据流-读书笔记
章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...
- Javascript模式(第五章对象创建模式)------读书笔记
一 命名空间模式 1 命名空间模式的代码格式 var MYAPP={ name:"", version:"1.0", init:function(){ } }; ...
- 《C++primer》v5 第3章 字符串、向量和数组 读书笔记 习题答案
本章问题 1.char *p="hello world";与char p[]="hello world"的问题. 简单说前者是一个指向字符串常量的指针,后者是一 ...
- 《移山之道》第十一章:两人合作 读书笔记 PB16110698 第六周(~4.15)
本周在考虑阅读材料时,我翻阅了<移山之道>,正好看到这一章:两人合作,心想:正好,我们正值结对作业的紧要关头,书中两人合作的宝贵经验和教诲应当对我们有很大帮助.于是,我开始一边在ddl苦 ...
随机推荐
- tomcat进程意外退出的问题分析(转)
原文链接:http://hongjiang.info/why-kill-2-cannot-stop-tomcat/ 节前某个部门的测试环境反馈tomcat会意外退出,我们到实际环境排查后发现不是jvm ...
- 寻路算法A*, JPS(跳点搜索)的一些杂谈
A*是一个比较经典的启发式寻路算法.是基于dijkstra算法,但是加入了启发函数,使路径搜索效率更高.实现起来很简单.不过要做到通用性高,比如支持各种不同类型的地图,甚至不仅仅是地图,而是个图结构如 ...
- WCF使用安全证书验证消息加密
首先安装 服务端安全证书 代码如下: // 下面第一行是安装证书,第二行是将证书列入信任 makecert.exe -sr LocalMachine -ss MY -a sha1 -n CN=lo ...
- Html遮罩层的显示(主要在于样式设置)
<html> <head> <title>aaa</title> <script type="text/javascript" ...
- 20145324 Java实验一
北京电子科技学院(BESTI) 实 验 报 告 课程:JAVA 班级:1453 姓名:王嘉澜 学号:20145324 成绩: 指导教师:娄嘉鹏 实验日期:2016.4.8 实验密级: 预习程度: 实验 ...
- 20145335《java程序设计》第5次实验报告
20145335郝昊实验五 java网络编程及安全 实验内容 1.掌握Socket程序的编写: 2.掌握密码技术的使用: 3.设计安全传输系统. 实验步骤 本次实验我的结对编程对象是20145307陈 ...
- 20135320赵瀚青LINUX第二章读书笔记
第二章-从内核出发 获取内核代码 使用git 获取最新提交到版本树的一个副本 $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/tor ...
- DDR4中的so-dimm 和component
so-dimm :Small Outline Dual In-line Memory Module (小型双列直插式内存模块) component:直接焊接的ddr4芯片
- RS232引脚,RS485引脚
1.RS232引脚 2.RS485引脚
- js中中括号,大括号使用详解
js中中括号,大括号使用详解 一.总结 一句话总结:{ } 是一个对象,[ ] 是一个数组 1.js大括号{}表示什么意思? 对象 { } 大括号,表示定义一个对象,大部分情况下要有成对的属性和值,或 ...