读书笔记 effective c++ Item 51 实现new和delete的时候要遵守约定
Item 50中解释了在什么情况下你可能想实现自己版本的operator new和operator delete,但是没有解释当你实现的时候需要遵守的约定。遵守这些规则并不是很困难,但是它们其中有一些并不直观,所以知道这些规则是什么很重要。
1. 定义operator new的约定
1.1 约定列举
我们以operator new开始。实现一个一致的operator new需要有正确的返回值,在没有足够内存的时候调用new-handling函数(见Item 49),并且做好准备处理没有内存可分配的情况。你也想避免无端的隐藏“正常”版本的new,但这是一个类接口的问题而不是实现需求问题;它会在Item 52中进行处理。
Operator new的返回值部分很简单,因为operator new事实上会尝试多次分配内存,在内次分配失败之后都会调用new-handling函数。这里的假设是new-handling函数可能会做一些事情来释放一些内存。只有在指向new-handling函数的指针为null的情况下,operator new才会抛出异常。
好奇的是,C++即使在请求0个byte的时候也需要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();
}
}
把请求0个byte当作请求1一个byte来进行处理的诡计看上去让人厌恶,但这是简单的实现并且合法,而且能够工作,无论如何,你对0个byte的请求会有多频繁呢?
对于伪代码中将new-handling函数指针设为null,然后迅速的将其复原的地方,你可能看上去比较怀疑。不幸的是,没有其他方法直接获得new-handling函数的指针,所以你必须调用set_new_handler来发现这个函数是什么。看上去粗糙但却是有效的,起码对于单线程来说是有效的。在多线程环境中,你可能需要某种类型的锁来安全的操作new-handling函数背后的(全局)数据结构。
在Item 49中讨论过了,在operator new中包含一个无限循环,上面的代码中将其展示了出来;“while(true)”就 表示一个无限循环。跳出循环的唯一方法是成功的分配内存或者让new-handling函数做到Item 49中描述的事情中的其中一件:有更多的内存可供分配,安装一个不同的new-handler,卸载new-handler,抛出一个异常,这个异常要么继承自bad_alloc要么源于失败返回。现在你应该清楚为什么new-handler必须做到这些事情中的一件的了,如果做不到,operator new中的循环永远不会终止。
1.2 由继承导致的问题
许多人没有意识到operator new成员函数是要被派生类继承的。这可能会导致一些有趣的并发症。在上面的operator new的伪代码中,注意函数尝试分配size个bytes。这再合理不过了,因为这是传递到函数中的参数。然而,正如Item 50中解释的,实现一个自定义内存管理器的最一般的原因就是为特定类的对象进行内存分配优化,而不是为类或者它的任何派生类。也即是,我们为类X提供了一个operaor new,这个函数的行为是为大小正好为sizeof(X)的对象进行调整,即不大也不小。然而由于继承的存在,可能发生通过调用基类中的operator new来为派生类对象分配内存:
class Base {
public:
static void* operator new(std::size_t size) throw(std::bad_alloc);
...
};
class Derived: public Base // Derived doesn’t declare
{ ... }; // operator new Derived *p = new Derived; // calls Base::operator new!
如果基类中的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
}
“等一下”我听见你大叫,“你忘记检查病态但是可能发生的情况,也就是size为0的情况了!”事实上,我没有忘记。测试仍然在那里,只不过是将测试并入size同sizeof(size)的测试之中了。C++用神秘的方式进行工作,其中之一的方式就是规定所有独立对象的大小不能为0(见Item 39)。根据定义,sizeof(Base)永远不会为0,所以如果size为0,内存请求将由::operator new来处理,它会以一种合理的方式来处理这个请求。
1.3 定义operator new[]的约定
如果你想在一个类中控制数组的内存分配,你需要实现operator new的数组形式,operator new[]。(这个函数通常被叫做“array new”,因为很难确定“operator new[]”该如何发音)。如果你决定实现operator new[],记住所有你正在做的是分配一大块原生内存——你不能对不存在于数组中的对象做任何事情。事实上,你甚至不能确定数组将会有多少对象。首先,你不会知道每个对象有多大。毕竟,很有可能通过继承来调用基类的operator new[]去为派生类对象数组分配内存,派生类对象通常比基类对象要大。因此,你不能假设在Base::operator new[]内部被放入数组的对象的大小为sizeof(Base),这就意味着你不能假设数组中对象的数量为(请求的字节数)/sizeof(Base)。第二,传递给operator new[]的参数size_t有可能比填入对象的内存更多,因为正如Item 16中解释的,动态分配的数组有可能包含额外的空间来存放数组元素的数量。
2. 定义operator delete的约定
当实现operator new的时候需要遵守的约定就这么多。对于operator delete,事情更加简单。所有你需要记住的是C++总是保证delete null指针是安全的,所以你需要遵守这个规定。下面是实现非成员 operator delete的伪代码:
void operator delete(void *rawMemory) throw()
{
if (rawMemory == ) return; // do nothing if the null
// pointer is being deleted
deallocate the memory pointed to by rawMemory;
}
这个函数的成员函数版本也是简单的,但是你需要确保检查正在被delete的对象的size。假设你的属于类的operator new将对错误数量内存的请求转发给了::operator new,你同样得将对“错误大小”的delete请求转发给::operator delete:
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; }
有趣的是,如果要被delete的对象派生自于一个没有虚析构函数的基类,那么传递给operator delete的size_t值有可能是不正确的。这就有了足够的理由来把你的基类中的析构函数声明为虚函数,但是Item 7中描述了第二个可能更好的原因。现在你需要注意的是如果你在基类中忽略了虚析构函数,operator delete函数的工作就有可能不正确。
3. 总结
- operator new应该包含一个无限循环来尝试分配内存,如果不能满足对内存的请求应该调用new-handler,应该处理对0个byte的请求。类的特定版本应该处理比预期更大的内存块的请求。
- operator delete中传递的指针如果是null,应该什么都不做。类特定版本需要处理比预期要大的内存块。
读书笔记 effective c++ Item 51 实现new和delete的时候要遵守约定的更多相关文章
- 读书笔记 effective c++ Item 16 成对使用new和delete时要用相同的形式
1. 一个错误释放内存的例子 下面的场景会有什么错? std::]; ... delete stringArray 一切看上去都是有序的.new匹配了一个delete.但有一些地方确实是错了.程序的行 ...
- 读书笔记 effective c++ Item 49 理解new-handler的行为
1. new-handler介绍 当操作符new不能满足内存分配请求的时候,它就会抛出异常.很久之前,它会返回一个null指针,一些旧的编译器仍然会这么做.你仍然会看到这种旧行为,但是我会把关于它的讨 ...
- 读书笔记 effective c++ Item 50 了解何时替换new和delete 是有意义的
1. 自定义new和delete的三个常见原因 我们先回顾一下基本原理.为什么人们一开始就想去替换编译器提供的operator new和operator delete版本?有三个最常见的原因: 为了检 ...
- 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库
1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...
- 读书笔记 effective c++ Item 1 将c++视为一个语言联邦
Item 1 将c++视为一个语言联邦 如今的c++已经是一个多重泛型变成语言.支持过程化,面向对象,函数式,泛型和元编程的组合.这种强大使得c++无可匹敌,却也带来了一些问题.所有“合适的”规则看上 ...
- 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数
关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...
- 读书笔记 effective c++ Item 11 在operator=中处理自我赋值
1.自我赋值是如何发生的 当一个对象委派给自己的时候,自我赋值就会发生: class Widget { ... }; Widget w; ... w = w; // assignment to sel ...
- 读书笔记 effective c++ Item 12 拷贝对象的所有部分
1.默认构造函数介绍 在设计良好的面向对象系统中,会将对象的内部进行封装,只有两个函数可以拷贝对象:这两个函数分别叫做拷贝构造函数和拷贝赋值运算符.我们把这两个函数统一叫做拷贝函数.从Item5中,我 ...
- 读书笔记 effective c++ Item 13 用对象来管理资源
1.不要手动释放从函数返回的堆资源 假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来, class Investment { ... ...
随机推荐
- 不需要密码的windows计划任务设置
使用windows计划任务定时做些事情,确实非常方便,但创建任务时老是需要设置密码,否则在执行任务时会报80070005的系统错误导致任务无法执行. 有时windows没设密码或当账户修改密码就必须修 ...
- 1641: [Usaco2007 Nov]Cow Hurdles 奶牛跨栏
1641: [Usaco2007 Nov]Cow Hurdles 奶牛跨栏 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 424 Solved: 272 ...
- 统计solr multivalued 字段中数目
问题是这样的:已有若干multivalued 字段, 需要统计出 multivalued 字段中数目. 比如 *DOC1* <doc> <arr name="multi&q ...
- 会话控制之session和cookie(20161107)
注:除了登录页面,每个页面,包括处理页面也要加,为了提高安全性 session尽量不用,因为很占内存 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML ...
- 使用spring webflow,在前台页面中如何显示后端返回的错误信息
刚刚接触spring webflow,相关的资料并不是很多,并且大都是那种入门的 .xml文件的配置. 用到的CAS 最新的4.0版本用的就是web-flow流,前台页面使用的是表单提交.于是我就碰到 ...
- javascript 将数字(金额)转成大写
将计算好的金额转换成大写,这些功能非常多,下面我改进了一下代码(原文在这里:http://www.cnblogs.com/zsanhong/p/3509464.html). /** * _SetNum ...
- iOS开发之Info.plist文件
建立一个工程后,会在Supporting files文件夹下看到一个“工程名-Info.plist”的文件,该文件对工程做一些运行期的配置,非常重要,不能删除 在旧版本Xcode创建的工程中,这个配置 ...
- Python全栈开发第13天
#多用户登录 import getpass #引用getpass import os #引用os import configparser #引用配置文件操作的库 count = 0 count_oth ...
- pygame加载中文名mp3文件出现error
好一阵子没有写东西了,最近几天在做一个基于Python pygame的音乐播放器,本来想做完了,再来发篇文章的,可越做越深,框架大致出来了,考虑周期比较长,也可能是我个人问题,做得比较慢,最近.下面来 ...
- JDK动态代理实现机制
=========================================== 原文链接: JDK动态代理实现机制 转载请注明出处! =========================== ...