[翻译] Effective C++, 3rd Edition, Item 51: 编写 new 和 delete 时要遵守惯例

VS Code配置MASM32汇编环境:从安装到调试的完整指南 本文提供了一份详尽的指南,教你如何在VS Code中配置MASM32汇编开发环境,实现从安装、环境变量设置到编写、编译和调试32位Windows汇编窗口程序的完整工作流。通过集成现代编辑器的便利性与经典汇编工具链,旨在降低学习门槛,提升底层开发效率。 阅读详情

Item 51: 编写 new 和 delete 时要遵守惯例

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.youkuaiyun.com/fatalerror99/

Item 50 讲解了什么时候你可能需要编写 operator new 和 operator delete 的你自己的版本,但是没有讲解当你这样做时必须遵循的惯例。这些规则并不难以遵循,但有一些不那么直观,所以了解它们是什么非常重要。

我们从 operator new 开始。实现一个符合惯例的 operator new 需要有正确的返回值,在没有足够的内存可用时调用 new-handling function(参见 Item 49),并做好应付无内存请求的准备。你还要避免无意中对 new 的“常规”形式的覆盖,虽然这更多的是一个 class interface(类接口)的问题,而并非是一个实现的需求,它将在 Item 52 讨论。

operator new 的返回值部分很容易。如果你能提供所请求的内存,你就返回一个指向它的指针。如果你不能,你应该遵循 Item 49 描述的规则并抛出一个 bad_alloc 类型的 exception(异常)。

然而,它也不完全那么简单,因为 operator new 实际上不止一次设法分配内存,每次失败后调用 new-handling function。在此假设 new-handling function 能做些事情释放一些内存。只有当指向 new-handling function 的指针为空时,operator new 才抛出一个 exception(异常)。

奇怪的是,C++ 要求即使请求零字节,operator new 也要返回一个合理的指针。(需要这种怪异的行为来简化语言的其它部分。)在这种情况下,一个 non-member(非成员)的 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 == 0) {                     // handle 0-byte requests
    size = 1;                          // 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(0);
    set_new_handler(globalHandler);

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

将零字节请求当作他们真的请求了一个字节来处理的窍门看起来很龌龊,但是它简单,合法,有效,无论如何,你估摸着的请求零字节这种事情发生的频率有多大呢?

你可能在不经意中还看到伪代码中将 new-handling function pointer 设置为空,然后又马上重置为它原来的值。遗憾的是,没有办法直接得到 new-handling function pointer,所以你必须调用 set_new_handler 以得知它是什么。拙劣,的确,但是也有效,至少对 single-threaded(单线程)代码没问题。在 multithreaded(多线程)环境中,你可能需要某种锁以便安全地摆布隐藏在 new-handling function 后面的(全局的)data structures(数据结构)。

Item 49 谈及 operator new 包含一个无限循环,而上面的代码明确地展示了这个循环,"while (true)" 差不多会尽其所能地无限做下去。跳出循环的唯一出路是内存被成功分配或 new-handling function 做了 Item 49 中描述的事情之一:使得更多的内存可用,安装一个不同的 new-handler,卸载 new-handler,抛出一个 bad_alloc 或从 bad_alloc 派生的 exception(异常),或不再返回。现在,new-handler 为什么要做这些事情之一已经很清楚了。如果它不这样做,operator new 内的循环永远不会停止。

很多人没有意识到 operator new member functions(成员函数)会被 derived classes(派生类)继承。这会引起一些有趣的复杂性。在前面的 operator new 伪代码中,注意那个函数设法分配 size 个字节(除非 size 是零)。因为它是传递给这个函数的 argument(实参),所以它有着明确的意义。然而,就像 Item 50 所讲的,编写一个自定义的内存管理器的最常见的原因之一是为了优化某个特定 class 的 objects 的分配,而不是某个 class 或它的任何 derived classes(派生类)的。也就是说,给定一个 class X 的 operator new,这个函数的行为通常是为大小为 sizeof(X) 的 objects 调谐的——绝不会更大或者更小。然而,由于 inheritance(继承),就有可能一个 base class(基类)中的 operator new 被调用来为一个 derived class(派生类)的 object 分配内存:

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!

如果 Base 的 class-specific(类专用)的 operator new 不是被设计成应付这种情况的——它很可能不是——它处理这种局面的最佳办法就是把这个请求“错误”内存量的调用甩给 standard 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 是零这种 pathological-but-nevertheless-possible(病态然而可能)的情况!”实际上,我没有,还有,当你大声抱怨的时候拜托不要使用连字符。测试依然在那,它只是与 size 和 sizeof(Base) 的比较合在了一起。C++ 工作在一些神秘的方式中,这些方式之一就是强制规定所有的独立 objects 都具有非零的大小(参见 Item 39)。根据定义,sizeof(Base) 绝不会是零,所以如果 size 是零,请求将转发给 ::operator new,而以一种合理的方式处置这个请求就成为那个函数的职责。

如果你想要在每一个 class 的基础上控制数组的内存分配,你需要实现 operator new 的专用于数组的兄弟,operator new[]。(这个 function 通常被叫做 "array new",因为要确定 "operator new[]" 如何发音实在是太难了。)如果你决定要编写 operator new[],记住你所做的全部是分配一大块 raw memory(裸内存)——你不能针对还不存在的数组中的 objects 做任何事情。实际上,你甚至不能确定数组中会有多少个 objects。首先,你不知道每个 object 有多大。毕竟,一个 base class(基类)的 operator new[] 通过继承可以被调用来为一个 derived class objects(派生类对象)的数组分配内存,而 derived class objects(派生类对象)通常都比 base class objects(基类对象)更大。

因此,在 Base::operator new[] 中,你不能断定每一个加到数组中的 object 的大小一定是 sizeof(Base),而这就意味着,你不能断定数组中的 objects 的数量是 (bytes requested)/sizeof(Base)。第二,传递给 operator new[] 的 size_t 参数可能比充满 objects 的内存还要大一些,因为,就像 Item 16 讲到的,dynamically allocated arrays(动态分配数组)可能包括额外的空间用于存储数组元素的数量。

编写 operator new 时,你需要遵循的惯例也就到此为止了。对于 operator delete,事情就更简单了,你需要记住的全部大约就是 C++ 保证删除空指针总是安全的,所以你需要遵循这个保证。下面是一个非成员的 operator delete 的伪代码:

void operator delete(void *rawMemory) throw()
{
  if (rawMemory == 0) return;            // do nothing if the null
                                         // pointer is being deleted

  deallocate the memory pointed to by rawMemory;
}

这个函数的成员版本也很简单,只是你必须确保检查被删除东西的大小。假设你的 class-specific(类专用)的 operator new 将“错误”大小的请求转发给 ::operator new,你也可以将“错误大小”的删除请求转发给 ::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 == 0) 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;
}

有趣的是,如果被删除的 object 是从一个缺少 virtual destructor(虚拟析构函数)的 base class(基类)派生出来的,C++ 传递给 operator delete 的 size_t 值也许是不正确的。这已经足够作为“确保你的 base classes(基类)拥有 virtual destructors(虚拟析构函数)”的原因了,除此之外,Item 7 描述了另一个,论证得更好的原因。至于当前,简单地记住如果你在 base classes(基类)中遗漏了 virtual destructors(虚拟析构函数),operator delete functions 可能无法正确工作。

Things to Remember

  • operator new 应该包含一个设法分配内存的无限循环,如果它不能满足一个内存请求,应该调用 new-handler,还应该处理零字节请求。class-specific(类专用)版本应该处理对比预期更大的区块的请求。
  • operator delete 如果收到一个空指针应该什么都不做。class-specific(类专用)版本应该处理比预期更大的区块。 
KMS服务器一键部署与激活实战指南 本文详细介绍了KMS服务器的一键部署与激活实战指南,包括快速入门、安装步骤、服务管理技巧及常见问题排查。特别针对Windows和Mac用户提供了Office激活的具体操作步骤,并分享了高级配置与优化方案,帮助IT管理员高效管理批量授权激活。 阅读详情

相关推荐

深入SVT SPI VIP配置:如何灵活控制CS间隔、数据帧大小与时钟频率(含代码片段)

本文深入探讨了SVT SPI VIP的高级配置技巧,包括精确控制CS信号间隔、突破数据帧大小限制以及动态时钟频率切换。通过实战代码片段和详细配置步骤,帮助验证工程师灵活应对复杂SPI协议验证场景,提升验证效率和覆盖率。特别适合需要处理非标准SPI时序和特殊数据帧的芯片验证项目。

weixin_27014595的博客 281

C++之编写new和delete时需要固守常规(51)---《Effective C++》

条款51:编写new和delete时需固守常规operator new中包含一个无限循环,而上述伪码明白表达出这个循环;“while(true)”就是那个无限循环,退出循环的唯一方法是:内存被成功分配或者new_hanlder从条款49的多个选择中选出一种进行选择,选择包括:让更多内存可用、安装另一个new_handler、卸载new_handler、抛出bad_alloc异常或者承认失败或者直接r

阿华Go,从现在开始的博客 530

人工智能情感分析实战:文本情绪识别

人工智能情感分析,特别是文本情绪识别,已成为理解和处理人类情感信息的重要工具。通过数据预处理、特征提取、模型训练与评估等步骤,我们可以有效地从文本数据中提取情感信息,为各种应用场景提供有价值的洞察。随着技术的不断进步和创新,情感分析的应用前景将更加广阔,为我们的生活和工作带来更多便利和机遇。

shejizuopin的博客 1098

深挖C++:类和对象(中)

(先进后出)

xys_678的博客 254

网卡Inter (R) Wi-Fi 6 AX201突然故障 错误代码10,无法找到无线网络,蓝牙也无效了

问题描述 说来也是坑,简直就莫名其妙。早上在工位上,电脑一切正常(一直用的是无线网络),然后周五准备开周会了,所以拔掉电脑外接设备(电源线,鼠标,外接显示器)拿起电脑到会议室。到了会议室之后,无线网断开了(但一般是不会断开的,都在公司范围内),所以选择重连,但发现已经没有无线网络列表了。 电脑机型:联想YOGA 14s 2021 初步尝试 1、关闭开始飞行模式 - 无果 2、重启并重启 (因为前两天已经提醒过有windows更新,趁这个机会就更新并重启了)- 无果 3、下载鲁大师、驱动精灵、联想驱动管

疯狂的蜗牛 5万+

UE5多人联机实战:从零搭建LAN游戏房间(含UMG界面配置)

本文详细介绍了如何在UE5中从零开始搭建一个完整的局域网(LAN)多人游戏房间系统。内容涵盖项目基础配置、UMG主菜单设计、会话创建与搜索的核心实现,并重点解析了创建会话的关键参数配置与常见陷阱,帮助开发者快速掌握UE5多人联机开发的核心流程。

weixin_29001683的博客 690

C++移动构造函数详解

移动构造函数是C++11引入的一种特殊构造函数,用于从临时对象(右值)"窃取"资源来初始化新对象,而不是进行昂贵的深拷贝。char* data;public:// 移动构造函数:参数是右值引用 &&// 关键:把源对象的资源置空。

拒绝AI文,努力在AI时代写出感动人心的好文章 394

AI辅助决策支持系统架构设计避坑:5个常见技术选型误区及正确方案

AI辅助决策支持系统(AI-DSS)是指通过人工智能技术(机器学习、自然语言处理、知识图谱等)赋能传统决策支持系统,帮助决策者在复杂业务场景中(如供应链优化、风险控制、医疗诊断、金融投资等)提升决策效率和准确性的系统。与传统BI工具不同,AI-DSS不仅能"展示数据",还能基于数据生成"决策建议"甚至"自动执行决策"。技术选型是AI-DSS架构设计的核心环节,涉及数据处理引擎、模型框架、算力资源、集成接口等关键组件的选择。效果不达标:模型预测精度低、决策建议不可用;成本失控:算力资源浪费、维护成本高企。

AIGC应用创新大全的博客 1384

ThunderKittens 深度解析:一个不基于 MLIR 的 C++ 模板 GPU DSL

Triton 是"让更多人能写 GPU kernel",ThunderKittens 是"让会写的人写得更爽"。前者用 MLIR 换易用性,后者用 C++ 模板换可控性。它们不是替代关系,而是覆盖了 GPU 编程光谱的两端。ThunderKittens 的出现,本质上是对"GPU DSL 必须基于 MLIR"这一默认假设的挑战。编译期类型系统本身就可以是一个强大的 DSL 基础设施,不需要中间 IR,不需要黑盒 pass,也能实现 tile 级抽象和零运行时开销。

Maxwell_Newton的博客 349

从零实现深度学习框架【实现自己的PyTorch(已完结)】

本着“凡我不能创造的,我就不能理解”的思想,此系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导。

日积月累,天道酬勤 6862

【06】新国都——新国都 嵌入式笔试,校招,题目记录及解析

本文总结了新国都嵌入式校招笔试题目及解析,内容涵盖C语言基础、Linux系统、数据结构与算法等多个方面。主要包括:1)不定项选择题10道,涉及字符串常量、蓝牙与GPRS区别、编译错误识别等;2)判断题10道,考察Linux权限、循环控制、内存管理等;3)填空题5道,包括编码计算、逻辑运算、宏定义等;4)简答题2道,解析全局变量引用和进程线程区别。题目难度适中,重点考察嵌入式开发基础知识和编程能力,并提供详细解析和扩展知识链接,对准备嵌入式相关笔试具有参考价值。

程序员,他们想的是什么?他们想的永远都是技术,他们崇尚的也永远都是技术。 1434

跟我学c++中级篇—类型定义的方式

本文系统阐述了编程中类型定义的重要性及c++中的实现方式。重点对比了宏定义、typedef与modern using三种机制,指出using在可读性、灵活性和模板支持上的优势,推荐优先使用。同时提醒开发者注意别名滥用、类型歧义、错误信息冗长等问题,强调统一规范与合理设计。最终结论:在现代c++开发中,应以using为主,兼顾简洁性与可维护性,避免陷入命名混乱与编译困境。

fpcc的专栏 88

1、vectorCast单元测试常用操作

进入软件主页面,点击file,选择set working directory,随便选择一个保存该项目的目录即可。

weixin_59334478的博客 2157

【C++——类和对象(上)】筑基篇:以封装奠定面向对象之基

本文系统梳理C++类与对象的核心知识点,涵盖类的定义、访问限定符(public/private)、类作用域、对象实例化及内存布局。重点解析封装思想、this指针机制、空类大小为1字节的原因,以及类与对象在内存中的存储差异。通过对比C语言结构体与C++类,揭示面向对象设计的优势:数据保护、接口统一、命名隔离与代码可维护性提升。内容由浅入深,适合作为学习笔记与复习参考。

2602_95684935的博客 497

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

C++ 函数模版

模板参数:● template ,T 就是类型占位符,名字可以自定义,习惯用T● 占位符可以在函数返回值、形参、函数内部当作类型使用使用要点模板代码一般写在头文件:编译器需要看到完整模板源码,才能在每个编译单元完成实例化全局函数、类内成员函数都可以写成模板实例化发生在编译阶段,不是运行时隐式实例化:调用时编译器自动推导实参类型,生成对应版本函数。

2501_94041724的博客 307

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

C++ 运行时多态与虚函数机制全面剖析【面试重点】

构成C++ 运行时多态必须是基类的指针或者引用调用虚函数。被调用的函数必须是虚函数,并且派生类必须对该虚函数完成重写/覆盖(Override)。只有基类的指针或引用才能既指向基类对象,又指向派生类对象(切片机制);而只有完成了虚函数重写,派生类与基类才具备不同的函数行为,达到“指向谁就调用谁”的效果。在类成员函数的前面加上virtual关键字修饰,该成员函数就成为了虚函数。public:// 成员函数前加 virtual 修饰,声明为虚函数。

Fuyo_0209的博客 450

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

#SQLite 数据库笔记一:(概念 / 终端指令 / SQL 语句 / C 编程 / 回调函数)

数据库 = 数据的仓库,假设数据是海量。能力说明存储数据持久化保存,程序退出数据还在统计数据、分组、排序数据管理增、删、改、查(CRUD)嵌入式设备上也会用数据库(单片机网关、路由器、工业 HMI 都常见),因为 sqlite 体积小、无需服务进程、单文件即可。数据库 = 库 > 表 > 记录(行) > 字段(列)sqlite3 = 开源、C 写的、单文件、免安装、最多 2T 的嵌入式关系型数据库。

lwq20051110的博客 251

1-10 PostgreSQL中多表连接全解:inner/left/right/full/cross join

本文基于 PostgreSQL 15+ 完整拆解 5 种主流连接:内连接、左连接、右连接、全连接、笛卡尔连接。结合可落地的测试数据,演示每种连接的执行逻辑、返回结果,重点剖析 NULL 值对 JOIN 匹配的核心影响,彻底解决多表连接数据丢失、匹配异常、结果错乱的问题。

zhangjw34的专栏 105

【C++ 基础入门】C++继承(下):问题、底层原理与组合

C++继承(菱形继承的问题、虚继承原理、继承与组合、构造顺序)

2601_96587588的博客 802

蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级 备考:莫队算法离线区间查询详解

备赛进行时:第十八届蓝桥杯软件赛报名已于 2026-10-08 10:00 开启、12-18 14:00 截止,省赛定在 2027-04-10(C/C++·Python 程序设计 9:00–13:00);中国电子学会 2026 年 12 月软件编程(C/C++ 一级至十级、Python 一级至六级)考级报名 9/23–12/2 16:00、统考 12-12;全国青少年信息素养大赛 2026—2027 赛季算法应用主题赛也已进入复盘练兵窗口。

IT_Scratch的博客 170

c++ explicit关键字详解

explicit。

拒绝AI文,努力在AI时代写出感动人心的好文章 151

825 mW 顶上 212 °C:ASM491S 的 105 °C/W、250 mA 短路电流与热关断

25 °C 环境里结温 34.1 °C,125 °C 环境里结温 134.1 °C——后者已经离 150 °C 只有 16 °C 了,但还在范围内。换个角度看同一件事:从 25 °C 和从 125 °C 起算,温升都是 86.6 °C,但一个到 111.6 °C(还在范围内),一个到 211.6 °C(远远超限)。这个算例里 20 °C/W 是我假设的,实际值取决于机壳材料、密封程度、有没有风扇,跨度可以从 5 °C/W 到 50 °C/W 以上。外面 50 °C 的机箱,盒内就是 63.9 °C。

ANSILIC的博客 393

《C++ 项目:手写大模型接入 SDK》Gemini接口设计(上):初始化、全量响应实现和测试

本文系统完成了Gemini模型通过OpenAI兼容API的接入开发,基于Apifox验证接口规范,设计并实现GeminiProvider类,涵盖初始化、状态检测、全量响应等核心功能。重点处理了代理配置强制校验、Bearer鉴权、JSON序列化与响应解析等工程细节,并通过GTest与CMake完成单元测试,验证链路可用性。项目遵循策略模式,保持与DeepSeek、ChatGPT等模型的统一抽象接口,显著提升SDK可扩展性。

2501_91275995的博客 398

【容斥原理计数】题解:P9489 ZHY的表示法_容斥原理_计数问题_前缀和问题转化_算法竞赛C++

给定 $ n $ 个正整数,求区间 $[l,r]$ 中有多少正整数 $ x $ 可表示为 $ \sum \lfloor \frac{y}{x_i} \rfloor $。通过二分确定最大 $ y $,转化为求 $ 1 \sim H $ 中是某些 $ x_i $ 倍数的数的个数,利用容斥原理与 DFS 枚举子集计算,结合 LCM 预判剪枝及去重优化,时间复杂度 $ O(2^n \log V) $,可高效处理 $ r \le 10^9 $ 的数据。

YLCHUP的博客 106

头歌 数据结构——C&C++线性表实训

本系列任务围绕线性表的链式实现,依次完成顺序构建、逆序构建、排序插入、查找、删除、求长度及栈、队列、集合等应用。通过动态内存分配与指针操作,掌握单链表的基本操作与实际应用,涵盖插入、删除、遍历、搜索等核心功能,最终实现数据结构在典型场景中的灵活运用。

一名敲代码的苦13程序员 244

字母移动游戏题解:贪心求最长可保留连续子串(附 C 实现)

本题核心思想是:最少移动步数 = 总长度 - 可保留的最长连续子串长度。未移动字母在T中必须为连续子串,在S中需保持相对顺序(即子序列)。通过枚举T的所有连续子串起点,贪心匹配S中的对应字符,求出最长可保留长度L,答案即为n−L。时间复杂度O(n²),空间复杂度O(n),适用于n≤1000。

冻柠茶飞冰走茶 592
上一篇: [翻译] Effective C++, 3rd Edition, Item 50: 领会何时替换 new 和 delete 才有意义
下一篇: [翻译] Effective C++, 3rd Edition, Item 52: 如果编写了 placement new,就要编写 placement delete
fatalerror99
博客等级 码龄24年 166粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值