effective c++ 49 了解new-handler的行为

news/2024/11/24 12:53:43/

effective c++ 49 了解new-handler的行为

我们在编写代码时,经常会使用new来创建对象。如果内存不够了,new会是怎样的行为?

默认的行为是,new将抛出一个异常。然而有时候我们不希望这样的默认行为,这个时候我们就需要new-handler。

本章节主要探讨了如何自定义new-handler。

分析

new-handler的介绍

作者在item-51节从给出了::operator new的一个伪代码,这个伪代码就比较清晰地显示了new-handler是何时被调用的。从下面的伪代码中我们也可以看到,new-handler如果被设置了,它将被循环调用。

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);//获取当前的new-handlerset_new_handler(globalHandler);//设置new-handlerif (globalHandler)(*globalHandler)();elsethrow std::bad_alloc();}
}

new-handler的例子

下面就是演示如何设置new-handler。这个实验其实也暗含了很多知识点,我刚刚开始的时候始终不能触发new-handler, 每次都是被linux oom给干掉了。

下面我便总结了这个实验如何才能做的起来。我们使用三个实验。

  • 在案例1中,程序触发overcommit规则而直接引起new失败。可以查看/proc/sys/vm/overcommit_memory, 如果为0,它允许overcommit,但过于明目张胆的overcommit会被拒绝,比如malloc一次性申请的内存大小就超过了系统总内存。例如我们下面的例子,我一次性申请10G,就触发了该规则,new失败,调用new-handler。
  • 在案例2中,程序虚拟内存用完触发new失败。new申请的都是虚拟内存,64位系统的虚拟内存高达128T,因此如果没有触发overcommit的话,就需要循环申请才有可能new失败,而且会需要很长时间,例如案例2。
  • 在案例3中,程序被oom给干掉了。如果你一次申请的内存过小的话,还没有到虚拟内存申请到128T, 物理内存就用完了(申请虚拟内存也需要占用少量物理内存),这个时候程序就会被linux oom给杀掉,就触发不了new-handler。(在我的机器上就被killed了,可以使用dmesg查看)

案例1:

// new_handler example
#include <iostream>     // std::cout
#include <cstdlib>      // std::exit
#include <new>          // std::set_new_handlervoid no_memory () {std::cout << "Failed to allocate memory!\n";std::exit (1);
}int main () {std::set_new_handler(no_memory);std::cout << "Attempting to allocate 10 GiB...";char* p = new char [10*1024*1024*1024];std::cout << "Ok\n";delete[] p;return 0;
}

案例2:

// new_handler example
#include <iostream>     // std::cout
#include <cstdlib>      // std::exit
#include <new>          // std::set_new_handler
#include <iostream>
#include <chrono>
#include <thread>
using namespace std::chrono_literals;void no_memory () {std::cout << "Failed to allocate memory!\n";std::exit (1);
}int main () {std::set_new_handler(no_memory);int count = 0;while(1){char* p = new char [1024*1024*1024];count++;std::cout << "current used "<< count << " G" << std::endl;}return 0;
}

案例3:

// new_handler example
#include <iostream>     // std::cout
#include <cstdlib>      // std::exit
#include <new>          // std::set_new_handler
#include <iostream>
#include <chrono>
#include <thread>
using namespace std::chrono_literals;void no_memory () {std::cout << "Failed to allocate memory!\n";std::exit (1);
}int main () {std::set_new_handler(no_memory);int count = 0;while(1){char* p = new char [1024*1024];}return 0;
}

new-handler的设计原则

从上面的伪代码中,我们知道,new-handler是嵌入在一个循环中的,因此当operator new无法满足内存申请时,它会不断调用new-handler函数,直到找到足够的内存。

由于会循环调用new-handler,因此设计new-handler时需要注意如下几点:

  • 让更多内存可被使用。
  • 安全其他new-handler
  • 卸载new-handler
  • 抛出bad_alloc的异常
  • 不返回,通常调用abort或者exit

为某个类设计new-handler

有时候我们需要为某个类的对象创建时候而自定义一个new-handler。我们通常可以像下面这样操作。

我们需要为Widget类重载operator new的操作符,并且在operator new操作符中使用RAII类NewHandlerHolder去自动resotre默认的new-handler。(RAII实现的一种auto-restore机制,很常用)

#include <new>
#include <iostream>class NewHandlerHolder
{
public:explicit NewHandlerHolder(std::new_handler nh) : handler(nh){}~NewHandlerHolder(){std::set_new_handler(handler);}private:std::new_handler handler;// Prevent copyingNewHandlerHolder(const NewHandlerHolder&);NewHandlerHolder& operator=(const NewHandlerHolder&);
};class Widget
{
public:static std::new_handler set_new_handler(std::new_handler p) throw();static void* operator new(std::size_t size) ;private:static std::new_handler currentHandler;char arr[(long)10*1024*1024*1024];//10G
};std::new_handler Widget::currentHandler = 0;std::new_handler Widget::set_new_handler(std::new_handler p) throw()
{std::new_handler oldHandler = currentHandler;currentHandler = p;return oldHandler;
}void* Widget::operator new(std::size_t size) 
{NewHandlerHolder h(std::set_new_handler(currentHandler));return ::operator new(size);
}void no_memory () {std::cout << "Failed to allocate memory!\n";std::exit (1);
}int main()
{Widget::set_new_handler(no_memory);while(1){Widget* w = new Widget;}
}

输出结果:

Failed to allocate memory!

为某个类设计new-handler(更通用的做法)

如果做的更加通用一点,如果有很多个类都希望可以设置new-handler, 就可以使用模板的方法。让有需要的类去继承模板类。

#include <new>
#include <iostream>#include <new>class NewHandlerHolder
{
public:explicit NewHandlerHolder(std::new_handler nh) : handler(nh){}~NewHandlerHolder(){std::set_new_handler(handler);}private:std::new_handler handler;// Prevent copyingNewHandlerHolder(const NewHandlerHolder&);NewHandlerHolder& operator=(const NewHandlerHolder&);
};template<typename T>
class NewHandlerSupport
{
public:static std::new_handler set_new_handler(std::new_handler p) throw();static void* operator new(std::size_t size) ;private:static std::new_handler currentHandler;
};template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;template<typename T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw()
{std::new_handler oldHandler = currentHandler;currentHandler = p;return oldHandler;
}template<typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size)
{NewHandlerHolder h(std::set_new_handler(currentHandler));return ::operator new(size);
}class Widget : public NewHandlerSupport<Widget>
{
private:char arr[(long)10*1024*1024*1024];
};void no_memory () {std::cout << "Failed to allocate memory!\n";std::exit (1);
}int main()
{Widget::set_new_handler(no_memory);while(1){Widget* w = new Widget;}
}

结果输出:

Failed to allocate memory!

nothrow使得new不抛出异常

我们可以使用std::nothrow来保证new不抛出异常。

class Widget{};
Widget* pw1 = new Widget;//如果new失败, 抛出std::bad_alloc
if(pw1 == 0)..
Widget* pw2 = new(std::nothrow) Widget;//如果失败,返回空指针
if(pw2 == 0)

但是nothrow对异常的强制保证性并不高。因为后续的构造函数还是可能会抛出异常。

总结

  • set_new_handler允许客户指定一个函数,在内存分配(虚拟内存)无法获得满足时被调用。
  • Nothrow new是一个颇为局限的工具,因为它只使用与内存分配,后继的构造函数调用还是可能抛出异常。

http://www.ppmy.cn/news/70814.html

相关文章

使用MinIO文件存储系统【完成图片上传保存】业务逻辑

目录 1&#xff1a;业务流程 2&#xff1a;接口实现 controller层 service层 1&#xff1a;业务流程 步骤一&#xff1a;前端进入上传图片的界面 步骤二&#xff1a;上传图片&#xff0c;请求到后端的媒资管理服务模块 步骤三&#xff1a;媒资管理服务将图片文件存储到m…

【大数据学习篇7】小试牛刀统计并且分析天猫数据

本项目基于搭建大数据环境&#xff0c;通过将数据存放在HDFS上&#xff0c;从HDFS中获取数据&#xff0c;然后根据实际需求通过Spark或Spark SQL对数据进行读取分析&#xff0c;将分析结果存储到HBase表中&#xff0c;最终通过 ECharts数据可视化工具基于Python Web平台实现数据…

Requests-get方法的使用

Requests-get方法使用 打开网页使用代码获取页面内容查看结果页面格式修改 爬取书名完整代码以及注释代码注释 翻页查询所有 以https://books.toscrape.com/网站为例&#xff1a; 打开网页 先把网页打开&#xff0c;然后右键检查&#xff0c;找到网络一栏&#xff0c;这个时候…

前端实现可拖拽课程表【纯HTML、CSS、JS】

前言 hello&#xff0c;今天实现点小动画&#xff0c;帮助学习理解Web api的拖拽效果&#xff0c;这里实现的是可拖拽的课程表&#xff01;# 效果图 附&#xff1a;作者没钱去除水印&#xff0c;就这样看一下简单的看一下效果吧&#xff01; 实现前言知识 这里我使用事件委…

怎么制作网站?手把手教你10个网站建设的步骤!

怎么制作网站&#xff1f;手把手教你10个网站建设的步骤&#xff01;网站建设需要进行10个步骤&#xff0c;首先要确定网站建设的目标&#xff0c;考虑用户、品牌信息和竞争对手等&#xff0c;避免方向错误。其次&#xff0c;绘制网站建设地图和原型&#xff0c;确定位置大小、…

ThreadLocal什么时候会出现OOM的情况

ThreadLocal什么时候会出现OOM的情况&#xff1f;为什么&#xff1f; ThreadLocal变量是维护在Thread内部的&#xff0c;这样的话只要我们的线程不退出&#xff0c;对象的引用就会 一直存 在。当线程退出时&#xff0c;Thread类会进行一些清理工作&#xff0c;其中就包含Th…

Java进阶-字符串的使用

1.API 1.1API概述 什么是API ​ API (Application Programming Interface) &#xff1a;应用程序编程接口 java中的API ​ 指的就是 JDK 中提供的各种功能的 Java类&#xff0c;这些类将底层的实现封装了起来&#xff0c;我们不需要关心这些类是如何实现的&#xff0c;只需要…

js对map排序,后端返回有序的LinkedHashMap类型时前端获取后顺序依旧从小到大的解决方法

js对map排序&#xff0c;后端返回有序的LinkedHashMap类型时前端获取后顺序依旧从小到大的解决方法 js对map排序&#xff0c;后端返回有序的LinkedHashMap类型时前端获取后顺序依旧从小到大的解决方法 [{"2020": [{"id": 39,"createTime": &quo…