EffectiveC++ | 49 了解 new-handler 行为

49:了解 new-handler 行为

operator new 失败后的行为

当 operator new 无法满足某一内存分配需求时,它会抛出异常。旧式编译器会返回 null 指针。

而抛出异常之前,它会先调用一个客户指定的错误处理函数: new-handler

为了指定这个“用以处理内存不足”的函数,客户必须调用 set_new_handler,那是声明于 的一个标准程序库函数:

namespace std {
	typedef void (*new_handler)();
	new_handler set_new_handler(new_handler p) throw();
}  // namespace std

// Example
void OutOfMem() {
  std::cerr << "Unable to satisfy request for memory" << std::endl;
  std::abort();
}

int main() {
  std::set_new_handler(OutOfMem);
  int *pBigDataArray = new int[10000000000000000L];
  pBigDataArray[0] = 213;
  delete[] pBigDataArray;
  pBigDataArray = nullptr;
  return 0;
}

//  结果:
Unable to satisfy request for memory
Aborted (core dumped)

new-handler 是一个 typedef,定义出一个指针指向函数,该函数没有参数也不返回任何东西。

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

set_new_handle 声明式尾部的 throw() 是一份异常明细,表明该函数不抛出任何异常。

当 operater new 无法满足内存申请时,它会不断调用 new-handler 函数,直到找到足够内存。

设计 new-handler

一个设计良好的 new-handler 函数必须考虑以下几点:

  1. **提供更多的可被使用的内存。**这会造成下次在operator new内下一次分配动作可能成功。实现这个策略的一个做法是在程序的开始阶段分配一大块内存,然后在第一次调用new-handler的时候释换给程序。
  2. 安装另一个的new-handler。如果当前的new-handler 无法取得更多可用内存,或许它知道另外哪个 new-handler 有此能力。做法之一是让new_handler修改会影响new-handler行为的static数据,namespace数据或 global 数据。
  3. 卸载new-handler,也就是向set_new_handler传递null指针。如果没有安装new-handler,operator new 会在内存分配不成功的时候会抛出异常;
  4. 抛出 bad-alloc(或派生自 bad-alloc )的异常
  5. 不返回调用abort或者exit

设计 class 专属 new-handlers

Method 1: 为 class 提供特定的 set_new_handle 和 operator new

C++ 并不支持为每一个 class 提供专属版本的 new_handler,可以为 class 提供自己实现的 set_new_handler 函数 和 operator new 函数。

  • 对于 set_new_handler ,我们根据参照默认实现即可
class Randy {
 private:
  static std::new_handler currentHandler_;

 public:
  static std::new_handler Set_new_handler(std::new_handler p) throw();
  static void* operator new(std::size_t size) throw(std::bad_alloc);
  ~Randy(){};
};

std::new_handler Randy::Set_new_handler(std::new_handler p) throw() {
  std::new_handler oldHandler = currentHandler_;
  currentHandler_ = p;
  return oldHandler;
}

// Static 成员必须在class 定义式之外被定义(除非它们是const 且是整数型)
// 在class 实现文件内初始化为0
std::new_handler Randy::currentHandler_ = 0;

Randy的 operator new 做以下事情:

  1. 调用标准版 set_new_handler ,告知 Randy 错误处理函数;
  2. 调用 global operator new,执行实际之内存分配;
  3. 如果 global operator new 能够分配足够1个Randy 对象所用内存, Randy的 operator new 会返回1个指针,指向分配所得。 Randy 的析构函数会管理 global new-handle,会自动将 operator new 被调用前的那个 global new-handler 恢复回来。

可以使用 RAII 管理 new_handler

class NewHandlerHolder {
 public:
  explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {} // 取得目前的 new-handler

  ~NewHandlerHolder() { std::set_new_handler(handler); } // 释放 new-handler

 private:
  std::new_handler handler; // 记录下来

  NewHandlerHolder(const NewHandlerHolder&); // 阻止 copying
  NewHandlerHolder& operator=(const NewHandlerHolder&);
};

class Randy {
 private:
  static std::new_handler currentHandler_;

 public:
  static std::new_handler Set_new_handler(std::new_handler p) throw();
  static void* operator new(std::size_t size) throw(std::bad_alloc) {
    // 安装 Randy 的new-handler
    NewHandlerHolder h(std::set_new_handler(currentHandler_));
    return ::operator new(size); // 分配内存或抛出异常。
  }; // 恢复 global new-handler
  ~Randy(){};
};

std::new_handler Randy::Set_new_handler(std::new_handler p) throw() {
  std::new_handler oldHandler = currentHandler_;
  currentHandler_ = p;
  return oldHandler;
}

std::new_handler Randy::currentHandler_ = 0;

void OutOfMem(); // 函数声明。此函数在 Randy 对象分配失败时被调用。

int main() {
  Randy::Set_new_handler(OutOfMem); // 设定 OutOfMem 为 Randy 的new-handing 函数
  Randy* pr1 = new Randy;  // 如果内存分配失败,调用 OutOfMem
  Randy::Set_new_handler(0); // 设定 Randy 专属 的new-handling 函数为null
  return 0;
}

Method 2:设计 template 版本 new_handler

一个更好的方式是使用 template 进行模板编程,确保每一个derived class 获得一个实体互异的currentHandler成员变量,即根据不同 class 进行特化和具现化。完整实现如下:

template <typename T>
class NewHandlerSupport {
public:
    static std::new_handler set_new_handler(std::new_handler p) throw() {
        const std::new_handler oldHandler = currentHandler;
        currentHandler = p;
        return oldHandler;
    }

    void* operator new(std::size_t Size) throw(std::bad_alloc) {
        NewHandlerHolder(std::set_new_handler(currentHandler));
        return ::operator new(Size);
    }
private:
    static std::new_handler currentHandler;
};

// 以下将每一个 currentHandler 初始化为 null
template <typename T>
std::new_handler  NewHandlerSupport<T>::currentHandler = 0;

class Randy: public  NewHandlerSupport<Randy> {
    ...  // 不必声明 set_new_handler 和 operator new
};

为什么需要 template?

你可能注意到 NewHandlerSupport template 从未使用其类型参数T。

实际上,T 的确不被需要,我们只是希望,继承自 NewHandlerSupport 的 每一个class,拥有实体互异的 NewHandlerSupport 复件(其currentHandler 成员)。

类型参数只是用来区分不同的派生类,Template 机制会自动为每一个 T 生成一份 CurrentHandler 成员。

请记住

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

欢迎关注公众号【三戒纪元】

公众号:三戒纪元

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值