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

2023-10-28

        条款50已解释什么时候你会想要写个自己的operator new和operator delete,但并没有解释当你那么做时必须遵守什么规则。这些规则不难奉行,但其中一些并不直观,所以知道它们究竟是些什么很重要。

        让我们从operator new开始。实现一致性operator new必须得返回正确的值,内存不足时必得调用new-handling函数(见条款 49),必须有对付零内存需求的准备,还需避免不慎掩盖正常形式的new——虽然比较偏近class的接口要求而非实现要求。正常形式的new描述于条款52。

        operator new的返回值十分单纯。如果它有能力供应客户申请的内存,就返回一个指针指向那块内存。如果没有那个能力,就遵循条款49描述的规则,并抛出一个bad_alloc异常。

        然而其实也不是非常单纯,因为operator new实际上不只一次尝试分配内存,并在每次失败后调用new-handling函数。这里假设new-handling函数也许能够做某些动作将某些内存释放出来。只有当指向new-handling函数的指针是null,operator new才会抛出异常。

        奇怪的是C++规定,即使客户要求0bytes,operator new也得返回一个合法指针。这种看似诡异的行为其实是为了简化语音其他部分。下面是个non-member operator new伪代码:

void* operator new(std::size_t size) throw(std::bad_alloc)
{
    using namespace std;
    if (size == 0) {
        size = 1;
    }
    while (true) {
        // 尝试分配size bytes
        if (分配成功)
        return (一个指针,指向分配得来的内存);
        // 分配失败;找出目前的new-handling函数(见下)
        new_handler globalHandler = set_new_handler(0);
        set_new_handler(globalHandler);

        if (globalHandler) {
            (*globalHandler)();
        }
        else {
            throw std::bad_alloc();
        }
    }
}

        这里的伎俩是把0 bytes申请量视为1 bytes申请量。看起来有点令人厌恶,但做法简单、合法、可行,而且比较客户多久才会发出一个0 bytes申请呢?

         条款49谈到operator new内含一个无穷循环,而上述伪代码明白表明这个循环;“while(true)”就是那个无穷循环。退出循环的唯一办法是:内存被成功分配或new-handling函数做了一件描述于条款49的事情:让更多内存可用、安装另一个new-handler、卸除new-handler、抛出bad_alloc异常(或派生物),或是承认失败而直接return。现在,对于new-handler为什么必须做出其中某些事你应该很清楚了。如果不那么做,operator new内的while循环永远不会结束。

        许多人没有意识到operator new成员函数会被derived classes继承。这会导致某些有趣的复杂度。注意上述operator new伪代码中,函数尝试分配size bytes(除非size是0)。那非常合理,因为size是函数接受的实参。然而就像条款50所言,写出定制型内存管理器的一个最常见理由是为针对某特定class的对象分配行为提供最优化,却不是为了该class的任何derived classes。也就是说,针对class X而设计的operator new,其行为很典型地只为大小刚好为sizeof(X)的对象而设计。然而一旦被继承下去,有可能base class的operator new被调用用以分配derived class对象:

class Base {
public:
    static void* operator new(std::size_t size) throw(std::bad_alloc);
    ...
};
class Derived: public Base  // 假设Derived未声明operator new
{ ... };
Derived* p = new Derived;   // 这里调用的是Base::operator new

        如果Base class专属的operator new并非被设计用来对付上述情况(实际上往往如此),处理此情势的最佳做法是将“内存申请量错误”的调用行为改采标准operator new,像这样:

void* Base::operator new(std::size_t size) throw(std::bad_alloc)
{
    if (size != sizeof(Base))             // 如果大小错误,
        return ::operator new(size);      // 令标准的operator new起而处理。
    ...                                   // 否则在这里处理。
}

        “等一下!”我听到你大叫,“你忘了检验size等于0这种病态但是可能出现的情况!”。C++裁定所有非附属(独立式)对象必须有非零大小(见条款39)。因此sizeof(Base)无论如何不能为零。

        如果你打算控制class专属之“arrays内存分配行为”,那么你需要实现operator new的array兄弟版:operator new[]。这个函数通常被称为“array new”,因为很难想出如何发音“operator new[]”。如果你决定写个operator new[],记住,唯一需要做的一件事就是分配一块为加工内存,因为你无法对array之内迄今尚未存在的元素对象做任何事情。实际上你甚至无法计算这个array将含有可能经由继承被调用,将内存分配给“元素为derived class对象”的array使用,而你当然知道,derived class对象通常比其base class对象大。

        因此,你不能在Base::operator new[]内假设array的每个元素对象的大小是sizeof(Base),这也就意味你不能假设array的元素对象个数是(bytes申请数)/sizeof(Base),此外,传递给operator new[]的size_t参数,其值有可能比“将被填以对象”的内存数量更多,因为条款16说过,动态分配的arrays可能包含额外空间来存放元素个数。

        这就是撰写operator new时你需要奉行的规矩。operator delete情况更简单,你需要记住的唯一事情就是C++保证“删除null指针永远安全”,所以你必须兑现这项保证。下面是non-member operator delete的伪代码

void operator delete(void * rawMemory) throw()
{
    if (rawMemory == 0) return;  // 如果将被删除的是个null指针,那就什么都不做
    // 现在,归还rawMemory所指的内存;
}

        这个函数的member版本也很简单,只需要多加一个动作检查删除数量。万一你的class专属的operator new将大小有误的分解行为转交::operator new指向,你也必须将大小有误的删除行为转交 ::operator delete执行:

class Base {
public:
    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 (rawMemeory == 0) return;
    if (size != sizeof(Base)) {
        ::operator delete(rawMemory);
        return;
    }
    // 现在,归还rawMemory所指的内存;
    return;
}

        有趣的是,如果即将被删除的对象派生自某个base class而后者欠缺virtual析构函数,那么C++传给operator delete的size_t数值可能不正确。这是“让你的base classes拥有virtual析构函数”的一个够好的理由;条款7还提过一个更好的理由。我就不岔开话题了,此刻只要你提高警觉,如果你的base classes遗漏virtual析构函数,operator delete可能无法正确运作。

请记住

  • operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。它也应该有能力处理0 bytes申请。Class operator delete应该收到null指针时不做任何事。Class专属版本则还应该处理“比正确大小更大的(错误)申请”。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

编写new和delete时需固守常规——条款51 的相关文章

  • Effective C++

    条款01 视C 为一个语言联邦 将 视为一个由相关语言组成的联邦而非单一语言 条款02 尽量以const enum inline替换 define define处理与预处理阶段 而非编译阶段 因此此条款也可称为 宁可以编译器替换预处理器比较
  • Effective C++学习笔记——宁以传引用替换传值

    目录 一 传值效率可能会很低 二 传值可能发生割裂问题 三 适用于传值的情况和注意事项 相关博客 C 引用知识归纳 一 传值效率可能会很低 我们假设有这样两个类 class Human public string name string s
  • 编写new和delete时需固守常规——条款51

    条款50已解释什么时候你会想要写个自己的operator new和operator delete 但并没有解释当你那么做时必须遵守什么规则 这些规则不难奉行 但其中一些并不直观 所以知道它们究竟是些什么很重要 让我们从operator ne
  • 考虑写出一个不抛异常的swap函数——条款25

    swap是个有趣的函数 原本它只是STL的一部分 而后成为异常安全性编程 exception safe programing 见条款29 的脊柱 以及用来处理自我赋值可能性 见条款11 的一个常见机制 由于swap如此有用 适当的实现很重要
  • 确定你的public继承塑模出is-a关系——条款32

    如果你令class D Derived 以public形式继承class B Base 你便是告诉C 编译器 以及你的代码读者 说 每一个类型为D的对象同时也是一个类型为B的对象 反之不成立 你的意识是B比D表现出更一般化的概念 而D比B表
  • 运用成员函数模板接受所有兼容类型——条款45

    所谓智能指针是 行为像指针 的对象 并提供指针没有的机能 例如条款13曾经提及std auto ptr和tr1 shared ptr如何能够被用来在正确时机自动删除heap based资源 STL容器的迭代器几乎总是智能指针 无疑地你不会奢
  • 透彻了解inlining的里里外外——条款30

    Inline函数 多棒的点子 它们看起来像函数 动作像函数 比宏好得多 见条款2 可以调用它们又不需要蒙受函数调用所招致的额外开销 你还能要求更多吗 你实际获得的比想到的还多 因为 免除函数调用成本 只是故事的一部分而已 编译器最优化机制通
  • 《Effective C++》学习笔记——区分接口继承和实现继承

    派生类public继承自基类 其中函数均是接口继承 实现继承又分为缺省继承与强制继承 对应着虚函数与非虚函数 我们在做项目时 对于任何事物都要抱有先描述再组织的心态 因此 当描述事物为其构建框架尤其是存在继承 is a 关系时 一定要搞清楚
  • 令operator=返回一个reference to *this——条款10

    关于赋值 有趣的是你可以把它们写成连锁形式 int x y z x y z 15 同样有趣的是 赋值采用右结合律 所以上述连锁赋值被解析为 x y z 15 这里15先被赋值给z 然后其结果 更新后的z 再被赋值给y 然后其结果 更新后的y
  • 尽可能延后变量定义式的出现时间——条款26

    只要你定义了一个变量而其类型带有一个构造函数或析构函数 那么当程序控制流 control flow 到达这个变量定义式时 你便得承受构造成本 当这个变量离开其作用域时 你便得承受析构成本 即使这个变量最终未被使用 仍需耗费这些成本 所以你应
  • 将文件间的编译依存关系降至最低——条款31

    假设你对C 程序的某个class实现文件做了些轻微修改 注意 修改的不是class接口 而是实现 而且只改private成分 然后重新建置这个程序 并预计只花数秒就好 毕竟只有一个class被修改 你按下 Build 按钮或键入make 或
  • Effective C++改善程序与设计的55个具体做法笔记

    Scott Meyers大师Effective三部曲 Effective C More Effective C Effective STL 这三本书出版已很多年 后来又出版了Effective Modern C More Effective
  • 《Effective C++》 全书内容提炼总结

    个人博客地址 https cxx001 gitee io 本文阅读说明 孔子云 取乎其上 得乎其中 取乎其中 得乎其下 取乎其下 则无所得矣 对于读书求知而言 这句古训教我们去读好书 最好是好书中的上品 经典书 Effective C 就是
  • Effective C++ 学习笔记 《六》

    Item 6 Explicitly disallow the use of compiler generated functions you do not want 其实这一节的内容是和item5紧密相连的 上一节的核心围绕着编译器会自动生
  • 将与参数无关的代码抽离templates——条款44

    Templates是节省时间和避免代码重复的一个奇方妙法 不再需要键入20个类似的classes而每一个带有15个成员函数 你只需键入一个class template 留给编译器去具现化那20个你需要的相关classes和300个函数 cl
  • 考虑virtual函数以外的其他选择——条款35

    假设你正在写一个视频游戏软件 你打算为游戏内的人物设计一个继承体系 你的游戏术语暴力砍杀类型 剧中人物被伤害或因其他因素而降低健康状态的情况并不罕见 你因此决定提供一个成员函数healthValue 它会返回一个整数 表示人物的健康程度 由
  • 写了placement new也要写placement delete——条款52

    placement new和placement delete并非C 兽栏中最常见的动物 如果你不熟悉它们 不要感到挫折或忧虑 回忆条款16和17 当你写一个new表达式像这样 Widget pw new Widget 共有两个函数被调用 一
  • 明智而审慎地使用多重继承——条款40

    当多重继承 multiple inheritance MI 进入设计景框 程序有可能从一个以上的base classes继承相同名称 如函数 typedef等等 那会导致较多的歧义机会 例如 class BorrowableItem 图书馆
  • 掌握 Effective C++ : 条款01

    背景 Effective C 是每个 C 程序员都应该读的经典之作 书中涵盖了 C 编程中的一系列最佳实践 包括了面向对象设计 模板 STL 异常处理等方面的内容 由于 C 的发展非常迅速 书中的某些内容可能已经过时 但依然是值得好好学习的
  • 复制对象时勿忘其每一个成分——条款12

    设计良好之面向对象系统 OO systems 会将对象的内部封装起来 只留两个函数负责对象拷贝 复制 那便是带着适切名称的copy构造函数和copy assignment操作符 我称它们为copying函数 条款5观察到编译器会在必要的时候

随机推荐

  • 计算机科学想象作文500,六年级想象作文600字

    第一篇 描写月亮的作文 你们听说过 超级月亮 吗 什么 没听说过 那么 我就带你们去看看前几天的超级月亮吧 今天 我像往常一样去广场滑滑板 忽然 我看一栋楼房的左 作文500字 未来的城市一场暴雨过后 阳光洒在充满绿意的城市 街道上没有积水
  • 09_Pandas从多个条件(AND,OR,NOT)中提取行

    09 Pandas从多个条件 AND OR NOT 中提取行 使用Pandas从多个条件 AND OR NOT 中提取行的方法 有以下2点需要注意 的使用 and or not的错误 使用比较运算符时 请将每个条件括在括号中 以下数据为例
  • 驱动名、设备名和设备文件名的关系

    编写一个驱动文件的时候生成一个name1 ko文件 这个name1就是驱动名 使用insmod name1 ko指令之后 用lsmod能看见一个名为name1的驱动 在调用了alloc chrdev region函数或register ch
  • 满分回答教你如何应对面试中项目经验这一难关

    给前端瓶子君加星标 提升前端技能 作者 亦逊 https juejin im post 5e7aed9c6fb9a07cac1d872d 前言 本篇文章的作者是来自阿里淘系用户增长前端团队的 亦逊 18年作为双非本科生通过层层面试 校招进入
  • CSS下划线与文字间距,下划线粗细以及下划线颜色的设置

    最开始的时候了解下划线的属性是 text decoration underline 1 但是 很遗憾的是 对于设计做的下划线用浏览器默认属性样式很难调整 使用这个属性并不能调整下划线与文字的间距 而且对于下划线的颜色也不好调整 而使用 u
  • 2014年仍然是DX11设备仿真和软引擎年

    先说点题外话 2014年1月比较浮躁 2月过年 回家后 与亲戚家的同龄人一比较 发现自己很废 不知道他们是不是在吹牛 想急功近利挣点钱 所以一度想进行OSG或者COCOS2DX 诚然 以后会进行这样的一种或两种 但是 今年不是时候 因为水平
  • MFC之创建插入符,写字,换行与退格11

    概述 我们按照前面文章根据向导创建项目 1 创建插入符 由于插入符是在创建窗口后并且做我们用户操作前需要使用 所以我们将插入符的创建放在OnCreate函数中即WM CRATE信号 int CInsertFuView OnCreate LP
  • reacthook的ref循环多个子组件

    父组件 ref值挂在这里 父子和兄弟都可以使用 const bodyRefs useRef
  • sklearn 随机森林(Random Forest)多分类问题

    模型 随机森林是集成学习算法的一种 sklearn更多的集成学习算法 RandomForestClassifier 参数详解 重要的参数有基分类器的个数 n estimators 特征选择算法 critirion 单个决策树的最大深度 ma
  • 使用wps2019快速翻译视频文字

    问题 视频中的英文如何翻译 如图 方法如下 1 使用wps 2019 截屏工具截取屏幕 2 使用 翻译文字 3 结果
  • SpringBoot+redis实现消息队列(发布/订阅)

    1 引入依赖
  • 2021解决ERROR:ModuleNotFoundError: No module named ‘sklearn‘

    2021解决ERROR ModuleNotFoundError No module named sklearn 在Python中 出现 no module named sklean 的原因是 没有正确安装sklean包 很多博文直接给出了这
  • 反向代理与 Real-IP 和 X-Forwarded-For

    开篇语 开涛新作 亿级流量网站架构核心技术 出版计划公布以来 博文视点遭受到一波又一波读者询问面世时间的DDos攻击 面对亿级流量的热情 感激之余 我们也很庆幸 这部作品质量的确过硬 不会辜负拥趸厚望 更有开涛的高度负责和体贴周到加持 让她
  • ARM学习之定时器Timer0实验

    Project Timer0实验 Writer SHOW Time 2011 10 16 Hareware 硬件平台 mini2440 J link Function 通过定时器0实现LED1以1s的时间间隔闪烁 Direction 这个实
  • 原来Github上的README.md文件这么有意思——Markdown语言详解

    转载 https blog csdn net zhaokaiqiang1992 article details 41349819 之前一直在使用github 也在上面分享了不少的项目和Demo 每次创建新项目的时候 使用的都是默认的READ
  • 用echarts实现3d饼图

    安装echarts和echarts gl npm install echarts npm install echarts gl echarts版本5 x的话需要对应echarts gl版本2 x echarts版本4 x的话需要对应echa
  • vue问题记录(一):将字符串中空格替换为换行符,以及将html字段的字符串 转换为 HTML

    时间比较紧迫 就不说废话了 直接代码 记录一下自己遇到的问题解决代码 1 将字符串中空格替换为换行符 代码如下 item next track time replace s nbsp ig br 2 将html字段的字符串转换为html 代
  • Mybatis学习笔记(八):使用注解开发

    8 使用注解开发 学习视频地址 使用注解开发 8 1 简单实现 1 注解在接口上实现 不需要Mapper xml配置 Select select from user List
  • C++之类模板

    前言 类封装了属性和方法 而这些属性和方法都有他们自己的数据类型 在有些特殊场景 我们希望我们的类里的这些属性和方法的类型能够在使用的时候再指定 因为我们并无法事先判断这个类的使用者会传什么类型给到这个类 例如 一个集合类 用户可以往集合中
  • 编写new和delete时需固守常规——条款51

    条款50已解释什么时候你会想要写个自己的operator new和operator delete 但并没有解释当你那么做时必须遵守什么规则 这些规则不难奉行 但其中一些并不直观 所以知道它们究竟是些什么很重要 让我们从operator ne