Item 51: Adhere to convention when writing new and delete.

Item 50介绍了怎样自己定义newdelete但没有解释你必须遵循的惯例。
这些惯例中有些并不直观,所以你须要记住它们!

  • operator
    new
    须要无限循环地获取资源。假设没能获取则调用”new handler”。不存在”new handler”时应该抛出异常。
  • operator
    new
    应该处理size
    == 0
    的情况。
  • operator
    delete
    应该兼容空指针。
  • operator
    new/delete
    作为成员函数应该处理size
    > sizeof(Base)
    的情况(由于继承的存在)。

外部operator new

Item 49指出了怎样将operator
new
重载为类的成员函数,在此我们先看看怎样实现一个外部(非成员函数)的operator
new
: operator
new
应当有正确的返回值。在内存不足时应当调用”new handler”,请求申请大小为0的内存时也能够正常运行。避免隐藏全局的(”normal form”)new

  • 给出返回值非常easy。

    当内存足够时。返回申请到的内存地址;当内存不足时。依据Item
    49
    描写叙述的规则返回空或者抛出bad_alloc异常。

  • 每次失败时调用”new handler”,并反复申请内存却不太easy。仅仅有当”new handler”为空时才应抛出异常。
  • 申请大小为零时也应返回合法的指针。同意申请大小为零的空间确实会给编程带来方便。

考虑到上述目标,一个非成员函数的operator
new
大致实现例如以下:

void * operator new(std::size_t size) throw(std::bad_alloc){
if(size == 0) size = 1;
while(true){
// 尝试申请
void *p = malloc(size); // 申请成功
if(p) return p; // 申请失败,获得new handler
new_handler h = set_new_handler(0);
set_new_handler(h); if(h) (*h)();
else throw bad_alloc();
}
}
  • size
    == 0
    时申请大小为1看起来不太合适,但它很easy并且能正常工作。况且你不会常常申请大小为0的空间吧?
  • 两次set_new_handler调用先把全局”new
    handler”设置为空再设置回来,这是由于无法直接获取”new handler”,多线程环境下这里一定须要锁。
  • while(true)意味着这可能是一个死循环。所以Item
    49
    提到,”new handler”要么释放很多其它内存、要么安装一个新的”new handler”,假设你实现了一个没用的”new handler”这里就是死循环了。

成员operator new

重载operator
new
为成员函数一般是为了对某个特定的类进行动态内存管理的优化,而不是用来给它的子类用的。 由于在实现Base::operator
new()
时,是基于对象大小为sizeof(Base)来进行内存管理优化的。

当然。有些情况你写的Base::operator
new
是通用于整个class及其子类的,这时这一条规则不适用。

class Base{
public:
static void* operator new(std::size_t size) throw(std::bad_alloc);
};
class Derived: public Base{...}; Derived *p = new Derived; // 调用了 Base::operator new !

子类继承Base::operator
new()
之后,由于当前对象不再是如果的大小。该方法不再适合管理当前对象的内存了。

能够在Base::operator
new
中推断參数size。当大小不为sizeof(Base)时调用全局的new

void *Base::operator new(std::size_t size) throw(std::bad_alloc){
if(size != sizeof(Base)) return ::operator new(size);
...
}

上面的代码没有检查size
== 0
。这是C++奇妙的地方,大小为0的独立对象会被插入一个char(见Item
39
)。 所以sizeof(Base)永远不会是0,所以size
== 0
的情况交给::operator
new(size)
去处理了。

这里提一下operator
new[]
。它和operator
new
具有相同的參数和返回值, 要注意的是你不要如果当中有几个对象。以及每一个对象的大小是多少,所以不要操作这些还不存在的对象。

由于:

  1. 你不知道对象大小是什么。上面也提到了当继承发生时size不一定等于sizeof(Base)

  2. size实參的值可能大于这些对象的大小之和。由于Item
    16
    中提到。数组的大小可能也须要存储。

外部operator delete

相比于new,实现delete的规则要简单非常多。唯一须要注意的是C++保证了delete一个NULL总是安全的,你尊重该惯例就可以。

相同地,先实现一个外部(非成员)的delete

void operator delete(void *rawMem) throw(){
if(rawMem == 0) return;
// 释放内存
}

成员operator delete

成员函数的delete也非常easy,但要注意假设你的new转发了其它size的申请,那么delete也应该转发其它size的申请。

class Base{
public:
static void * operator new(std::size_t size) throw(std::bad_alloc);
static void operator delete(void *rawMem, std::size_t size) throw();
};
void Base::operator delete(void *rawMem, std::size_t size) throw(){
if(rawMem == 0) return; // 检查空指针
if(size != sizeof(Base)){
::operator delete(rawMem);
}
// 释放内存
}

注意上面的检查的是rawMem为空,size是不会为空的。

事实上size实參的值是通过调用者的类型来推导的(假设没有虚析构函数的话):

Base *p = new Derived;  // 如果Base::~Base不是虚函数
delete p; // 传入`delete(void *rawMem, std::size_t size)`的`size == sizeof(Base)`。

假设Base::~Base()声明为virtual,则上述size就是正确的sizeof(Derived)
这也是为什么Item 7指出析构函数一定要声明virtual

Item 51:写new和delete时请遵循惯例的更多相关文章

  1. 读书笔记 effective c++ Item 51 实现new和delete的时候要遵守约定

    Item 50中解释了在什么情况下你可能想实现自己版本的operator new和operator delete,但是没有解释当你实现的时候需要遵守的约定.遵守这些规则并不是很困难,但是它们其中有一些 ...

  2. 条款八: 写operator new和operator delete时要遵循常规

    自己重写operator new时(条款10解释了为什么有时要重写它),很重要的一点是函数提供的行为要和系统缺省的operator new一致.实际做起来也就是:要有正确的返回值:可用内存不够时要调用 ...

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

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

  4. 覆盖equals方法时请遵守通用约定

    覆盖equals方法时请遵守通用约定   覆盖equals方法看起来很简单,但是有许多覆盖方式会导致错误,并且后果很严重.最容易避免这种类问题的方法就是不覆盖equals方法,在这种情况下,类的每个实 ...

  5. 条款16:成对使用new和delete时要使用相同的形式

    请牢记: 如果在new表达式中使用[],必须在相应的delete表达式中也使用[]. new[]  对应  delete[] 如歌在new表达式中不适用[],一定不要在相应的delete表达式中使用[ ...

  6. 条款16:成对使用new和delete时,采取相同的形式

    问题聚焦:     我们都知道,new和delete要成对使用,但是有时候,事情往往不是按我们预期的那样发展.     对于单一对象和对象数组,我们要分开考虑.     遇到typedef时,也需要搞 ...

  7. Effective C++(16) 成对使用new和delete时要采取相同的形式

      问题聚焦:     我们都知道,new和delete要成对使用,但是有时候,事情往往不是按我们预期的那样发展.     对于单一对象和对象数组,我们要分开考虑     遇到typedef时,也需要 ...

  8. 将目录下面所有的 .cs 文件合并到一个 code.cs 文件中,写著作权复制代码时的必备良药

    将目录下面所有的 .cs 文件合并到一个 code.cs 文件中,写著作权复制代码时的必备良药 @echo off echo 将该目录下所有.cs文件的内容合并到一个 code.cs 文件中! pau ...

  9. 重载delete时的那点事

    重载delete时的那点事 C++的异常处理机制就会自动用与被使用 的operator new匹配的operator delete来释放内存(补充一点:在operator new中抛出异常不会导致这样 ...

随机推荐

  1. 谋哥:搞APP,做得累的都不对!

    最近谋哥(微信viyi88)我刚加入“秦王会”,思想收到猛烈地冲击,各位大佬的思维有时候会让我大脑短路,收获不少.同时,我也慢慢发现我一直平静的 心开始浮躁,我发现苗头不对,于是开始静下心来.静下心, ...

  2. day03_11 if语句实现猜年龄01

    老男孩猜年龄游戏 age_of_princal = 56 guess_age = int( input(">>:") ) #以下为伪代码 ''' if guess_ag ...

  3. 一个TensorFlow例子

    一个TensorFlow的例子 import tensorflow as tf x = tf.constant(1.0, name='input') w = tf.Variable(0.8, name ...

  4. Welcome-to-Swift-04集合类型(Collection Types)

    Swift提供了两种集合类型来存放多个值——数组(Array)和字典(Dictionary).数组把相同类型的值存放在一个有序链表里.字典把相同类型的值存放在一个无序集合里,这些值可以通过唯一标识符( ...

  5. 【bzoj3680】吊打XXX 随机化

    题目描述 gty又虐了一场比赛,被虐的蒟蒻们决定吊打gty.gty见大势不好机智的分出了n个分身,但还是被人多势众的蒟蒻抓住了.蒟蒻们将n个gty吊在n根绳子上,每根绳子穿过天台的一个洞.这n根绳子有 ...

  6. idea部署项目到远程tomcat

    之前做项目,一直都是把本地的源码上传到svn,服务器是通过ant或者maven脚本来编译的生成项目的.每次都要单独登录接服务器进行项目的部署和发布,感觉特别繁琐.(特别是在有几套服务器的情况下,简直就 ...

  7. Linux System Programming 学习笔记(九) 内存管理

    1. 进程地址空间 Linux中,进程并不是直接操作物理内存地址,而是每个进程关联一个虚拟地址空间 内存页是memory management unit (MMU) 可以管理的最小地址单元 机器的体系 ...

  8. 【CF1015C】Songs Compression(贪心)

    题意: 给定n和m,n组(a[i],b[i]),每一组a[i]可以压缩为b[i],求最少只需要压缩几个,使得m可以存下所有数据,无解输出-1 思路:按差贪心,排序 #include<cstdio ...

  9. 开始学习es6(二) let 与 const 及 块级作用域

    1.var JavaScript中,我们通常说的作用域是函数作用域,使用var声明的变量,无论是在代码的哪个地方声明的,都会提升到当前作用域的最顶部,这种行为叫做变量提升(Hoisting) cons ...

  10. Linux 虚拟地址与物理地址的映射关系分析【转】

    转自:http://blog.csdn.net/ordeder/article/details/41630945 版权声明:本文为博主(http://blog.csdn.net/ordeder)原创文 ...