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的时候要遵守约定的更多相关文章

  1. 读书笔记 effective c++ Item 16 成对使用new和delete时要用相同的形式

    1. 一个错误释放内存的例子 下面的场景会有什么错? std::]; ... delete stringArray 一切看上去都是有序的.new匹配了一个delete.但有一些地方确实是错了.程序的行 ...

  2. 读书笔记 effective c++ Item 49 理解new-handler的行为

    1. new-handler介绍 当操作符new不能满足内存分配请求的时候,它就会抛出异常.很久之前,它会返回一个null指针,一些旧的编译器仍然会这么做.你仍然会看到这种旧行为,但是我会把关于它的讨 ...

  3. 读书笔记 effective c++ Item 50 了解何时替换new和delete 是有意义的

    1. 自定义new和delete的三个常见原因 我们先回顾一下基本原理.为什么人们一开始就想去替换编译器提供的operator new和operator delete版本?有三个最常见的原因: 为了检 ...

  4. 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库

    1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...

  5. 读书笔记 effective c++ Item 1 将c++视为一个语言联邦

    Item 1 将c++视为一个语言联邦 如今的c++已经是一个多重泛型变成语言.支持过程化,面向对象,函数式,泛型和元编程的组合.这种强大使得c++无可匹敌,却也带来了一些问题.所有“合适的”规则看上 ...

  6. 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数

    关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...

  7. 读书笔记 effective c++ Item 11 在operator=中处理自我赋值

    1.自我赋值是如何发生的 当一个对象委派给自己的时候,自我赋值就会发生: class Widget { ... }; Widget w; ... w = w; // assignment to sel ...

  8. 读书笔记 effective c++ Item 12 拷贝对象的所有部分

    1.默认构造函数介绍 在设计良好的面向对象系统中,会将对象的内部进行封装,只有两个函数可以拷贝对象:这两个函数分别叫做拷贝构造函数和拷贝赋值运算符.我们把这两个函数统一叫做拷贝函数.从Item5中,我 ...

  9. 读书笔记 effective c++ Item 13 用对象来管理资源

    1.不要手动释放从函数返回的堆资源 假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来, class Investment { ... ...

随机推荐

  1. Bat小试牛刀

    前天版本发布后同事才发现有点小瑕疵,当然这是前期的设计和测试没到位造成的.撇开这些不说,我想说的是知识面广一点,做起事情来可能更得心应手些. 大致是这样的,由于版本的迭代,导致发布的程序只能清除一部分 ...

  2. 从零开始学JavaWeb

    引言   记得上学时,有位导师说过一句很经典的话:"编程语言只是工具,最重要的是掌握思想." 笔者一直主要从事.net领域的开发工作.随着工作阅历的丰富,越来越深刻的理解当年导师说 ...

  3. 1642: [Usaco2007 Nov]Milking Time 挤奶时间

    1642: [Usaco2007 Nov]Milking Time 挤奶时间 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 582  Solved: 33 ...

  4. 1588: [HNOI2002]营业额统计

    1588: [HNOI2002]营业额统计 Time Limit: 5 Sec  Memory Limit: 162 MBSubmit: 9203  Solved: 3097[Submit][Stat ...

  5. 前端总结·基础篇·JS(二)数组深拷贝、去重以及字符串反序和数组(Array)

    目录 这是<前端总结·基础篇·JS>系列的第二篇,主要总结一下JS数组的使用.技巧以及常用方法. 一.数组使用 1.1 定义数组 1.2 使用数组 1.3 类型检测 二.常用技巧 2.1 ...

  6. Java基础之路(三)下--流程控制语句

    上次我们说了流程控制语句中的if语句,那本次就来聊聊switch语句. if else语句可以用来描述"二叉路口",而switch可用于"多叉路口"的情况. s ...

  7. Metadata Service 架构详解 - 每天5分钟玩转 OpenStack(165)

    下面是 Metadata Service 的架构图,本节我们详细讨论各个组件以及它们之间的关系. nova-api-metadata nova-api-metadata 是 nova-api 的一个子 ...

  8. 看了一个烟花的html作品 --引用:http://www.w3cfuns.com/blog-5444049-5404365.html

    最近老大想把项目改成响应式,一直在学习没时间更新博客.今天看到一个原生的js烟花项目,感觉很好,把记下来,以后把妹用. [run]<!DOCTYPE html><html>&l ...

  9. Entity Framework — ( Database First )

    什么是Entity Framework Entity Framework是微软以 ADO.NET 为基础所发展出来的对象关系对应 (O/R Mapping) 解决方案.将数据存储从域对象自动映射到关系 ...

  10. windows phone 8.1开发:磁铁|Tile更新

    原文出自:http://www.bcmeng.com/tile/ 上一篇给大家分享了toast通知操作的方法,这一篇文章我们就来看windows phone 8.1开发中的磁铁更新.磁铁是window ...