当前位置: 首页 > article >正文

Effective C++ 规则49:了解 new-handler 的行为

1、背景

在 C++ 中,new 运算符用于动态分配内存。然而,当内存分配失败时,程序默认会抛出一个 std::bad_alloc 异常。为了更灵活地处理这种情况,C++ 提供了一种机制,允许开发者自定义内存分配失败时的行为。这就是 new-handler 机制的作用。new-handler 是一种回调函数,当 new 操作符无法分配内存时,运行时系统会调用这个回调函数。通过设置自定义的 new-handler,开发者可以在内存分配失败时执行一些特定的操作,例如:

  • 释放未使用的内存。
  • 记录错误日志。
  • 尝试再次分配内存。
  • 终止程序或采取其他恢复措施。
    new-handler 的设计使程序能够优雅地应对内存分配失败,而不是直接崩溃。

2、如何使用new-handler

C++ 提供了两个与 new-handler 相关的标准函数:

  • std::set_new_handler,用于设置全局的 new-handler 函数,定义在头文件 中。
  • std::new_handler,是一个函数指针,指向当前的 new-handler,以下是 std::set_new_handler 的函数签名:
#include <new>
std::new_handler std::set_new_handler(std::new_handler new_p) noexcept;
  • 参数:new_p 是指向新的 new-handler 函数的指针。
  • 返回值:返回之前设置的 new-handler。
  • 异常保证:此函数不抛出任何异常。

3、示例代码

以下是一个完整示例,展示如何使用 std::set_new_handler 设置自定义的 new-handler。

#include <iostream>
#include <new> // std::set_new_handler
#include <cstdlib> // std::abort

// 自定义 new-handler 函数
void myNewHandler() {
    std::cerr << "Memory allocation failed. Attempting to recover..." << std::endl;

    // 可以选择释放资源或记录日志
    // 示例:直接终止程序
    std::abort();
}

int main() {
    // 设置全局的 new-handler
    std::set_new_handler(myNewHandler);

    try {
        // 故意尝试分配超大内存
        size_t size = static_cast<size_t>(-1);
        int* p = new int[size];
        delete[] p;
    } catch (const std::bad_alloc& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }

    return 0;
}

输出结果:Memory allocation failed. Attempting to recover…
因为,当 new 运算符无法分配内存时,运行时调用了自定义的 myNewHandler,打印错误信息并终止程序。如果未设置 new-handler,程序默认会抛出 std::bad_alloc 异常。
有时,我们可能希望为某个特定的类设置专属的 new-handler,而不是全局生效。这可以通过类的静态成员函数实现,代码如下:

#include <iostream>
#include <new>
#include <cstdlib>

class Widget {
public:
    // 设置类专属的 new-handler
    static void setNewHandler(std::new_handler p) noexcept {
        currentHandler = p;
    }

    // 重载 operator new,使用类专属的 new-handler
    static void* operator new(size_t size) {
        std::new_handler oldHandler = std::set_new_handler(currentHandler);
        void* mem;
        try {
            mem = ::operator new(size);
        } catch (...) {
            std::set_new_handler(oldHandler); // 恢复旧的 handler
            throw; // 继续抛出异常
        }
        std::set_new_handler(oldHandler); // 恢复旧的 handler
        return mem;
    }

private:
    static std::new_handler currentHandler;
};

// 定义静态成员
std::new_handler Widget::currentHandler = nullptr;

// 自定义 new-handler
void customNewHandler() {
    std::cerr << "Widget allocation failed. Cleaning up..." << std::endl;
    std::abort();
}

int main() {
    Widget::setNewHandler(customNewHandler);

    try {
        // 尝试分配超大内存
        Widget* w = new Widget[static_cast<size_t>(-1)];
        delete[] w;
    } catch (const std::bad_alloc& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }

    return 0;
}

4、使用new-handler的注意事项

  • 确保 new-handler 是无异常的:new-handler 必须是 noexcept 函数。如果 new-handler 抛出异常,标准库会调用 std::terminate。
  • 处理恢复逻辑:在 new-handler 中,尝试释放未使用的内存或其他资源,尽量避免直接终止程序。
  • 避免递归调用:如果在 new-handler 中尝试再次分配内存,可能导致递归调用 new-handler,最终导致程序崩溃。
  • 线程安全性:std::set_new_handler 会影响全局的 new-handler,因此在多线程环境下使用时需要格外小心。

5、总结

new-handler 是 C++ 提供的一种强大的机制,允许开发者在内存分配失败时定制处理逻辑。通过全局或类专属的 new-handler,程序可以优雅地应对内存不足的情况。尽管如此,new-handler 的使用需要谨慎。合理地释放资源、避免递归调用以及处理异常是实现一个可靠 new-handler 的关键。


http://www.kler.cn/a/520318.html

相关文章:

  • 一文讲解Java中的接口和抽象类
  • RNN实现阿尔茨海默症的诊断识别
  • 一次端口监听正常,tcpdump无法监听到指定端口报文问题分析
  • SpringBoot统一功能处理
  • 《Chart.js 饼图:深度解析与最佳实践指南》
  • (5)STM32 USB设备开发-USB键盘
  • C语言之图像文件的属性
  • 60.await与sleep的原理分析 C#例子 WPF例子
  • 八股文 (一)
  • GD32的GD库开发
  • 删除全表数据sql
  • 企业微信SCRM开创客户管理新纪元推动私域流量高效转化
  • Linux 命令行十六进制编辑器:高效操作二进制文件
  • 解决老游戏不兼容新系统win10win11问题
  • MFC结构体数据文件读写实例
  • 使用飞书群机器人监控服务器GPU使用率
  • 动手学图神经网络(3):利用图神经网络进行节点分类 从理论到实践
  • 理解C++中的右值引用
  • ui-automator定位官网文档下载及使用
  • 第25篇 基于ARM A9处理器用C语言实现中断<一>
  • 无人机微波图像传输数据链技术详解
  • STM32使用VScode开发
  • XML外部实体注入--漏洞利用
  • 亚博microros小车-原生ubuntu支持系列:13 激光雷达避障
  • 基于OSAL的嵌入式裸机事件驱动框架——软件定时器osal_timer
  • 自由学习记录(32)