effective c++ 笔记 条款49-52

条款 49:了解 new-handler 的行为

当operator new无法满足某一内存分配需求时,会不断调用一个客户指定的错误处理函数,即所谓的 new-handler,直到找到足够内存为止
new-handler 是一个 typedef,指向一个无参数值无返回值的函数。可以通过 set_new_handler 函数去指定客户想要的 new-handler。
set_new_handler 函数接受一个新的 new-handler 参数,返回被替换掉的 new-handler 函数

namespace std {using new_handler = void(*)();new_handler set_new_handler(new_handler) noexcept;    // 返回值为原来持有的 new-handler
}

设计良好的 new-handler 函数:

  1. 让更多的内存可被使用: 可以让程序一开始执行就分配一大块内存,而后当 new-handler 第一次被调用,将它们释还给程序使用,造成operator new的下一次内存分配动作可能成功。
  2. 安装另一个 new-handler:如果当前的new-handler不能够为你提供更多的内存,可能另外一个new-handler可以。即在当前的new-handler的位置上安装另外一个new-handler(通过调用set_new_handler)。下次operator new调用new-handler函数的时候,它会调用最近安装的。这需要让new_handler修改会影响new-handler行为的static数据,命名空间数据或者全局数据
  3. 卸除 new-handler: 将nullptr传给set_new_handler,使operator new在内存分配不成功时抛出异常
  4. 出 bad_alloc(或派生自 bad_alloc)的异常: 这样的异常不会被operator new捕捉,会被传播到内存分配处
  5. 不返回: 通常调用std::abort或std::exit

以不同的方式处理内存分配的情况,比如按不同的 class 进行处理。c++ 并不支持为每一个 class 提供专属版本的 new_handler,要用静态成员

public:static std::new_handler set_new_handler(std::new_handler p) noexcept;static void* operator new(std::size_t size);
private:static std::new_handler currentHandler;
};
// 做和 std::set_new_handler 相同的事情
std::new_handler Widget::set_new_handler(std::new_handler p) noexcept {std::new_handler oldHandler = currentHandler;currentHandler = p;return oldHandler; 
}
void* Widget::operator new(std::size_t size) {auto globalHandler = std::set_new_handler(currentHandler);  // 切换至 Widget 的专属 new-handlervoid* ptr = ::operator new(size);                           // 分配内存或抛出异常std::set_new_handler(globalHandler);                        // 切换回全局的 new-handlerreturn globalHandler;
}
std::new_handler Widget::currentHandler = nullptr;

以对象管理资源的方法:

class NewHandlerHolder {
public:explicit NewHandlerHolder(std::new_handler nh): handler(nh) {}~NewHandlerHolder() {std::set_new_handler(handler);}private:std::new_handler handler;
};

Widget::operator new的实现可改为

void* Widget::operator new(std::size_t size) noexcept{NewHandlerHolder h(std::set_new_handler(currentHandler));return ::operator new(size);
}

Widget的客户调用 :

void OutOfMem();
Widget::set_new_handler(OutOfMem);
auto pw1 = new Widget;              // 若分配失败,则调用 OutOfMem
Widget::set_new_handler(nullptr);
auto pw2 = new Widget;              // 若分配失败,则抛出异常

上述代码每个class要实现自己的set_new_handler和operator new。可以用template。建立起一个“mixin”风格的基类,让其派生类继承它们所需的set_new_handler和operator new,并且使用模板确保每一个派生类获得一个实体互异的currentHandler成员变量

template<typename T>
class NewHandlerSupport {       // “mixin”风格的基类
public:static std::new_handler set_new_handler(std::new_handler p) noexcept;static void* operator new(std::size_t size);...                         // 其它的 operator new 版本,见条款 52
private:static std::new_handler currentHandler;
};template<typename T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p) noexcept {std::new_handler oldHandler = currentHandler;currentHandler = p;return oldHandler;
}template<typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size) {auto globalHandler = std::set_new_handler(currentHandler);void* ptr = ::operator new(size);std::set_new_handler(globalHandler);return globalHandler;
}template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = nullptr;class Widget : public NewHandlerSupport<Widget> {
public:...                         // 不必再声明 set_new_handler 和 operator new
};

此处的模板参数T并没有真正被当成类型使用,而仅仅是用来区分不同的派生类,使得模板机制为每个派生类具现化出一份对应的currentHandler
即 CRTP(curious recurring template pattern,奇异递归模板模式),也被用于实现静态多态

template <class Derived> 
struct Base {void Interface() {static_cast<Derived*>(this)->Implementation();      // 在基类中暴露接口}
};
struct Derived : Base<Derived> {void Implementation();                                  // 在派生类中提供实现
};

C++ 保留了传统的“分配失败便返回空指针”的operator new,称为 nothrow new,通过std::nothrow对象来使用

Widget* pw1 = new Widget;                   // 如果分配失败,抛出 bad_alloc
if (pw1 == nullptr) ...                     // 这个测试一定失败
Widget* pw2 = new (std::nothrow) Widget;    // 如果分配失败,返回空指针
if (pw2 == nullptr) ...                     // 这个测试可能成功

条款50:了解new和delete的合理替换时机

定制operator new和operator delete的理由:
用来检测运行上的错误:如果将“new 所得内存”delete 掉却不幸失败,会导致内存泄漏;如果在“new 所得内存”身上多次 delete 则会导致未定义行为。如果令operator new持有一串动态分配所得地址,而operator delete将地址从中移除,就很容易检测出上述错误用法
另外自定义new分配超额内存,在额外空间放置特定签名/byte pattern。在delete时检查是否不变;反之,肯定存在“overruns”(写入点在分配区块尾部之后)或“unferruns”(写入点在分配区块头部之前),delete也可log那个指针。例如:

static const int signature = 0xDEADBEEF;              // 调试“魔数”
using Byte = unsigned char;void* operator new(std::size_t size) {using namespace std;size_t realSize = size + 2 * sizeof(int);         // 分配额外空间以塞入两个签名void* pMem = malloc(realSize);                    // 调用 malloc 取得内存if (!pMem) throw bad_alloc();// 将签名写入内存的起点和尾端*(static_cast<int*>(pMem)) = signature;*(reinterpret_cast<int*>(static_cast<Byte*>(pMem) + realSize - sizeof(int))) = signature;return static_cast<Byte*>(pMem) + sizeof(int);    // 返回指针指向第一个签名后的内存位置
}

这段代码不保证内存对齐,许多地方不遵守c++规范,见条款51
收集使用上的统计数据: 定制 new 和 delete 动态内存的相关信息:分配区块的大小分布,寿命分布,FIFO(先进先出)、LIFO(后进先出)或随机次序的倾向性,不同的分配/归还形态,使用的最大动态分配量等等。
增加分配和归还的速度:泛用型分配器通常比定制分配器慢。类专属的分配器可以做到“区块尺寸固定”,例如 Boost 提供的 Pool 程序库。又例如,编译器所带的内存管理器是线程安全的,但如果你的程序是单线程的,你也可以考虑写一个不线程安全的分配器来提高速度
降低缺省内存管理器带来的空间额外开销:泛用型分配器通常比定制分配器使用更多内存。因为常常在每一个分配区块身上招引某些额外开销。针对小型对象而开发的分配器(例如 Boost 的 Pool 程序库)本质上消除了这样的额外开销
弥补缺省分配器中的非最佳内存对齐:许多计算机体系架构要求特定的类型必须放在特定的内存地址上,如果没有奉行这个约束条件,可能导致运行期硬件异常,或者访问速度变低。std::max_align_t用来返回当前平台的最大默认内存对齐类型,对于malloc分配的内存,其对齐和max_align_t类型的对齐大小应当是一致的,但若对malloc返回的指针进行偏移,就没有办法保证内存对齐
C++11 中,内存对齐相关方法
将相关对象成簇集中:如果知道特定的某个数据结构往往被一起使用,又希望在处理这些数据时将“内存页错误(page faults)”的频率降至最低,可以考虑为此数据结构创建一个堆,将它们成簇集中在尽可能少的内存页上。一般可以使用 placement new 达成这个目标条款52
获得非传统的行为:如分配和归还共享内存,这些事情只能被 C API 完成,则可以将 C API 封在 C++ 的外壳里,写在定制的 new 和 delete 中

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

编写自己的new:存不足时必须不断调用 new-handler,如果无法供应客户申请的内存,就抛出std::bad_alloc异常。即使客户需求为0字节,operator new也得返回一个合法的指针

void* operator new(std::size_t size) {using namespace std;if (size == 0)      // 处理0字节申请size = 1;       // 将其视为1字节申请while (true) {if (...)        // 如果分配成功return ...; // 返回指针指向分配得到的内存// 如果分配失败,调用目前的 new-handlerauto globalHandler = get_new_handler(); // since C++11if (globalHandler) (*globalHandler)();else throw std::bad_alloc();}
}

如果子类未声明自己的operator new,会从父类继承过来,使的子类使用了父类new分配方式。但子类与父类的大小多数时候是不同的,因此成员函数版本:

void* Base::operator new(std::size_t size) {if (size != sizeof(Base))return ::operator new(size);    // 转交给标准的 operator new 进行处理...
}

此时无需检测大小是否为0,因为类必须有非零大小条款39
如果要实现operator new[],即array new,唯一要做的就是分配一块未加工的原始内存。因为无法对尚未存在的元素对象做任何事,甚至无法计算含有多少个对象
编写自己的delete:删除空指针永远安全

void operator delete(void* rawMemory) noexcept {if (rawMemory == 0) return;// 归还 rawMemory 所指的内存
}

成员函数版本

void Base::operator delete(void* rawMemory, std::size_t size) noexcept {if (rawMemory == 0) return;if (size != sizeof(Base)) {::operator delete(rawMemory);    // 转交给标准的 operator delete 进行处理return;}// 归还 rawMemory 所指的内存
}

条款 52:写了 placement new 也要写 placement delete

placement new:如果你的new接收的参数除了必定有的size_t外还有其他

void* operator new(std::size_t, std::ostream& logStream);
auto pw = new (std::cerr) Widget;

elete同理
当创建对象时,会先进行new函数,然后调用构造函数,如果构造出现异常,就需要delete,否则内存泄漏但客户手上的指针仍未指向该被归还的内存,因此由c++系统本身调用delete。系统需要知道哪个delete该被调用
当抛出异常时,运行期系统会寻找参数个数和类型都与 operator new 相同的某个 operator delete。 placement delete 只有在 placement new 的调用构造函数异常时才会被系统调用,即使对一个用 placement new 申请出的指针使用 delete,也绝不会调用 placement delete。因此如果要处理 placement new 相关的内存泄漏问题,我们必须同时提供一个正常版本的 delete 和 placement 版本的 delete。前者用于构造期间无异常抛出,后者用于构造期间有异常抛出

class Widget {
public:static void* operator new(std::size_t size, std::ostream& logStream);   // placement newstatic void operator delete(void* pMemory);                             // delete 时调用的正常 operator deletestatic void operator delete(void* pMemory, std::ostream& logStream);    // placement delete
};

还要注意同名函数遮掩调用的问题

class Base {
public:static void* operator new(std::size_t size, std::ostream& logStream);...
};
auto pb = new Base;             // 无法通过编译!
auto pb = new (std::cerr) Base; // 正确

同理,子类的operator new会遮掩global和父类继承的operator new版本:

class Derived : public Base {
public:static void* operator new(std::size_t size);...
};auto pd = new (std::clog) Derived;  // 无法通过编译!
auto pd = new Derived;              // 正确

除非目的就是禁用,否则要确保这些默认形式对定制类型依然可用

void* operator(std::size_t) throw(std::bad_alloc);           // normal new
void* operator(std::size_t, void*) noexcept;                 // placement new
void* operator(std::size_t, const std::nothrow_t&) noexcept; // nothrow new

可以准备一个基类,包含所有的正常版本new和delete

class StadardNewDeleteForms{
public:// normal new/deletestatic void* operator new(std::size_t size){return ::operator new(size);}static void operator delete(void* pMemory) noexcept {::operator delete(pMemory);}// placement new/deletestatic void* operator new(std::size_t size, void* ptr) {return ::operator new(size, ptr);}static void operator delete(void* pMemory, void* ptr) noexcept {::operator delete(pMemory, ptr);}// nothrow new/deletestatic void* operator new(std::size_t size, const std::nothrow_t& nt) {return ::operator new(size,nt);}static void operator delete(void* pMemory,const std::nothrow_t&) noexcept {::operator delete(pMemory);}
};

凡是需要自定义的class,可以继承该类并使用using声明式条款33

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/731814.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式-行为型模式-职责链模式

在软件系统运行时&#xff0c;对象并不是孤立存在的&#xff0c;它们可以通过相互通信协作完成某些功能&#xff0c;一个对象在运行时也将影响到其他对象的运行。行为型模式&#xff08;Behavioral Pattern&#xff09;关注系统中对象之间的交互&#xff0c;研究系统在运行时对…

逻辑回归原理skearn简单实现

理论解释 理解“逻辑回归”的名字&#xff0c;需要拆开理解&#xff0c;拆成两部分&#xff1a;“逻辑”和“回归” “回归””来自其父级&#xff0c;即广义线性回归模型。 “逻辑”如何理解&#xff1f;Logistic 不应该翻译成“逻辑”&#xff0c;因其语义来自Logarithm&…

C++:多态

目录 1、多态的概念 2、多态如何实现 虚函数 虚函数的重写 虚函数重写的两个例外&#xff1a; c11中 override与final 3、重载 、重写、重定义的区别。 1、重载 2、重写 3、重定义 接口继承和实现继承 4、动态绑定与静态绑定 5、虚函数表 1、单继承中虚函数表 …

Uber/Google Golang编码标准深度分析

良好的代码风格对于开发优秀的产品至关重要&#xff0c;本文通过分析比较三部流传甚广的Golang代码风格指南&#xff0c;介绍了Go代码风格要点&#xff0c;并介绍了通过工具实现代码检查的方式。原文: Mastering Go: In-Depth Analysis of Uber and Google’s Coding Standards…

C++矢量运算与java矢量运算

矢量运算 概述&#xff1a; 矢量运算是一种基于向量的数学运算&#xff0c;它遵循特定的法则。以下是矢量运算的一些基本原理&#xff1a; 矢量加法&#xff1a;可以使用平行四边形法则或三角形法则来执行。当两个矢量相加时&#xff0c;可以将它们的起点放在同一个点上&…

RabbitMQ篇

1.初始MQ 1.1. 同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&#xff0c;…

初阶数据结构:排序(学习笔记)

目录 1. 各种排序算法的分类2. 插入排序2.1 直接插入排序2.2 希尔排序 3. 选择排序3.1 选择排序3.2 堆排序4. 交换排序4.1 冒泡排序4.2 快速排序4.2.1 霍尔法&#xff08;hoare&#xff09;4.2.2 挖坑法&#xff08;hole&#xff09;4.4.3 前后指针法4.4.4 补充&#xff1a;非递…

Armadillo:C++线性代数运行库的版本更迭史

API 添加和更改的历史记录 文章目录 API 稳定性和版本政策&#xff1a;每个版本的添加和更改列表&#xff1a;版本12.8&#xff1a;版本12.6&#xff1a;版本12.4&#xff1a;版本12.2&#xff1a;版本12.0&#xff1a;版本11.4&#xff1a;版本11.2&#xff1a;版本11.0&…

存货计价方式 比较-移动平均和批次计价

SAP常用的存货计价方式有 标准价格移动平均价格批次计价 标准价格常用于制造企业&#xff0c;今天的方案比较主要集中在销售型企业常用的移动平均价和批次计价 批次计价&#xff1a; 移动平均&#xff1a; 两种计价方式的Pros&Cons 比较 批次计价 移动平均优点 1…

超好用的一键生成原创文案方法

在现代社会中&#xff0c;原创文案不管是在营销中&#xff0c;还是在品牌推广中都起着至关重要的作用。然而&#xff0c;对于许多人来说&#xff0c;创作出令人印象深刻且引人注目的原创文案并不容易。但随着技术的发展&#xff0c;我们现在可以利用一键生成原创文案的方法来帮…

黑马java-JavaSE进阶-java高级技术

1.单元测试 就是针对最小的功能单元方法&#xff0c;编写测试代码对其进行正确性测试 2.Junit单元测试框架 可以用来对方法进行测试&#xff0c;它是第三方公司开源出来的 优点&#xff1a; 可以灵活的编写测试代码&#xff0c;可以针对某个方法执行测试&#xff0c;也支持一键…

基于springboot的水果购物商城管理系统(程序+文档+数据库)

** &#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;** 一、研究背景…

Pinctrl子系统_04_Pinctrl子系统主要数据结构

引言 本节说明Pinctrl子系统中主要的数据结构&#xff0c;对这些数据结构有所了解&#xff0c;也就是对Pinctrl子系统有所了解了。 前面说过&#xff0c;要使用Pinctrl子系统&#xff0c;就需要去配置设备树。 以内核面向对象的思想&#xff0c;设备树可以分为两部分&#x…

rabbitmq3

指定通过通道将消息发送给哪个消息队列 同一个通道可以向不同的队列发送消息的&#xff0c;如果你绑定的队列和发布消息的队列不一致也是可以的&#xff0c;这个才是真正的发布消息去具体的某一个队列&#xff1a; 如果队列没有持久化&#xff0c;就不会把这个消息队列保存在磁…

stm32学习记录-5.2PWM输出控制sg90舵机角度

源码连接&#xff1a;https://gitee.com/HL12334/stm32-learning-code 前提知识&#xff1a; 1.定时器中断 1.关键概念 1.1pwm输出 1.常用术语 OC&#xff08;output compare&#xff09;输出比较CNT&#xff08;counter&#xff09;&#xff0c;定时器中用于计数的寄存器…

时序报告Report_timing_summary之一步精通配置选项使用

目录 一、前言 二、配置选项概览图 三、配置选项 3.1 Options 3.1.1 report 3.1.2 path limits 3.1.3 path display 3.2 Advanced 3.2.1 report 3.2.3 miscellaneous 3.3 Timer Settings 3.4 共有部分 四、工程示例 4.1 工程设计代码 4.2 约束文件 4.3 Option…

Nginx常用命令总结及常见问题排查

连续更新挑战第4天… 目录 常用启停命令Nginx 常见问题Nginx 如何忽略非标准http头检测?Nginx websocket代理Nginx 临时缓存不够导致下载文件失败Nginx 没有临时缓存目录权限导致下载文件失败Nginx非root用户启动无法使用80端口或者报无权限异常路由重写怎么配置?nginx 根据…

Linux系统架构----nginx的访问控制

nginx的访问控制 一、nginx基于授权的访问控制概述 Nginx与Apache一样&#xff0c;可以实现基于用户权限的访问控制&#xff0c;当客户端想要访问相应的网站或者目录时&#xff0c;要求用户输入用户名和密码&#xff0c;才能正常访问配置步骤生成用户密码认证文件 &#xff1…

qt带后缀单位的QLineEdit

QLineEditUnit.h #pragma once #include <QLineEdit> #include <QPushButton>class QLineEditUnit : public QLineEdit {Q_OBJECT public:QLineEditUnit(QWidget* parent Q_NULLPTR);~QLineEditUnit();//获取编辑框单位QString UnitText()const;//设置编辑框单位…

STM32的启动流程分析 和 一些底层控制的原理

阅读引言&#xff1a; 阅读本文之后&#xff0c; 你将对单片机&#xff0c; 甚至是嵌入式系统&#xff0c; 或者是传统的PC机系统的启动流程有一个大致的了解&#xff0c; 本文更加偏向于单片机的启动流程分析。 目录 一、基础知识 1.STM32系列的微控制器&#xff08;mcu&…