EffectiveC++-条款49:了解 new-handler 行为

本文详细讲解了C++中new-handler函数的作用,如何设置和使用set_new_handler,以及如何通过模板实现类特定的内存处理策略。重点讨论了new抛出异常与nothrow的区别,以及模板在处理多态new操作中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. 内容

  1. 当你调用 operator new 函数,程序无法满足某一内存需求时,它会抛出异常。老旧的编译器会返回 null 指针。而抛出异常之前,程序会先调用一个 operator new 错误处理函数,名叫 new-handler

    new-handler 是一个 typedef,指向一个无参数值无返回值的函数。我们可以通过 set_new_handler 函数去指定客户想要的 new-handler。

    set_new_handler 函数接受一个新的 new-handler 参数,返回被替换掉的 new-handler 函数

  2. 一个设计良好的 new-handler 函数必须考虑以下事情:

    • 让更多内存被使用。一个实现策略是程序一开始就分配大量内存,当 new-handler 被调用时再返还给程序使用。
    • 安装另一个 new-handler。如果存在其他 new-handler 可以提供内存,那么可以调用其他的 new-handler。
    • 卸除 new-handler。当无法解决问题时,使用 null 指针赋予 set_new_handler 函数,operator new 会在内存分配不成功时抛出异常。
    • 抛出 bad-alloc,或派生自 bad-alloc 的异常。
    • 不返回,调用 abort() 或 exit()。
  3. 有时候你或许希望以不同的方式处理内存分配的情况,比如按不同的 class 进行处理,但是 C++ 并不支持为每一个 class 提供专属版本的 new_handler,好在我们可以模仿这一行为,只要我们为 class 实现自己的 set_new_handler 函数 和 operator new 函数即可。

    • 对于 set_new_handler ,我们根据参照默认实现即可:

          static std::new_handler SetNewHandler(std::new_handler NewHandler) throw() {
              const std::new_handler OldHandler=std::set_new_handler(NewHandler);
              CurrentHandler=OldHandler;
              return OldHandler;
          }
      
    • 对于 operator new,我们要做以下事情。

      • 调用标准版 set_new_handler 安装我们自定义的 new-handler,将返回的标准版 new-handler 保存起来。
      • 调用标准版 operator new。如果标准版 operator new 异常,那么会调用我们自定义的 new-handler 处理函数。
      • 调用标准版 set_new_handler 重新安装标准版的 new-handler。

      为了确保可以重新安装标准版 new-handler,我们可以采用条款13所说以对象管理资源的方法:

      class NewController {
      public:
          explicit NewController(std::new_handler InHandler): Handler(InHandler) {}
      
          ~NewController() {
              std::set_new_handler(Handler);
          }
      
      private:
          std::new_handler Handler;
      };
      

      所以 operator new 实现如下:

      void* operator new(std::size_t Size) throw(std::bad_alloc) {
      	NewController(std::set_new_handler(CurrentHandler));
      	return ::operator new(Size);
      }
      
  4. 但是上述代码还是不够简洁,每一个 class 都要自己实现一个 set_new_handler 和 operator new 版本。一个更好的方式是使用 template 进行模板编程,然后根据不同 class 进行特化和具现化。完整实现如下:

    template <typename T>
    class NewHandlerSupport {
    public:
        static std::new_handler SetNewHandler(std::new_handler NewHandler) throw() {
            const std::new_handler OldHandler = std::set_new_handler(NewHandler);
            CurrentHandler = OldHandler;
            return OldHandler;
        }
    
        void* operator new(std::size_t Size) throw(std::bad_alloc) {
            NewController(std::set_new_handler(CurrentHandler));
            return ::operator new(Size);
        }
    private:
        static std::new_handler CurrentHandler;
    };
    template <typename T>
    std::new_handler  NewHandlerSupport<T>::CurrentHandler = nullptr;
    
    class FDemo:public  NewHandlerSupport<FDemo> {
        
    };
    
    inline void TryWithNew() {
        FDemo::SetNewHandler([]() {
            std::cout<<"内存不够啦"<<"\n";
        });
        FDemo *Demos=new FDemo[1000123123100000]();
    }
    

    注意,当 operator new 无法满足内存申请时,它会不断调用 new-handler 函数,直到找到足够内存或异常退出

    当然,你想说为什么我们需要 template?我们似乎并没有使用到模板参数,是的,T 的确不被需要,我们只是希望,继承自 NewHandlerSupport 的 class 拥有各自的 CurrentHandler 成员。类型参数只是用来区分不同的派生类,然后 template 机制会自动为每一个 T 具现化一份 CurrentHandler 成员,即使它是 static 的

    也许你的焦虑还来自于 template class 导致的多重继承,可以先看看条款40。

  5. 早些版本的 C++ 要求 operator new 函数在无法分配足够内存时返回 nullptr 指针,现在新一代的 C++ 要求抛出 bad_alloc 异常。为了兼容,C++ 提供了另一种形式负责传统的行为:new(std::nothrow)xxx; 保证 new 分配失败后返回 nullptr 指针。

    但即使 new 不抛出异常,紧接着的 xxx 构造函数的调用也有可能抛出异常。异常行为依旧会产生,所以几乎没有运用 nothrow 的必要。

二. 总结

  1. set_new_handler 允许客户指定一个函数,在内存分配无法获得满足时被调用。
  2. Nothrow new 是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值