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

探讨了C++中operator new内存分配失败时的处理机制,介绍了如何使用set_new_handler设置自定义错误处理函数,以及如何实现类专属的new-handler。

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

        operator new无法满足某一内存分配需求时,它会抛出异常。以前它会返回一个null指针,某些旧式编译器目前也还那么做。你还是可以取得旧行为,但本条款最后才会进行这项讨论。

        当operator new抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的new-handler。为了指定这个“用以处理内存不足”的函数,客户必须调用set_new_handler,那是声明于<new>的标准程序库函数:

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

        如你所见,new_handler是个typedef,定义出一个指针指向函数,该函数没有参数也不返回任何东西。set_new_handler则是“获得一个new_handler并返回一个new_handler”的函数。set_new_handler声明式尾端的“throw()”是一份异常明细,表示该函数不抛出任何异常——虽然事实更有趣些,详见条款29

        set_new_handler的参数是个指针,指向operator new无法分配足够内存时该被调用的函数。其返回值也是个指针,指向set_new_handler被调用前正在执行(但马上就要被替换)的那个new-handler函数。

        你可以这样使用set_new_handler:

// 以下是当operator new无法分配足够内存时,该被调用的函数
void outOfMem()
{
    std::cerr<<"Unable to statisfy request for memeory\n";
    std::abort();
}

int main()
{
    std::set_new_handler(outOfMem);
    int* pBigDataArray = new int[100000000L];
    ...
}

        就本例而言,如果operator new无法为100000000个整数分配足够空间,outOfMem会被调用,于是程序在发出一个信息之后夭折(abort)。

        当operator new无法满足内存申请时,它会不断调用new-handler函数,直到找到足够内存。引起反复调用的代码显示于条款51,这里的高级描述已足够获得一个结论,那就是设计良好的new-handler函数必须做以下事情:

  • 让更多内存可被使用。这便造成operator new内的下一次内存分配动作可能成功。实现此策略的一个做法是,程序一开始执行就分配一大块内存,而后当new-handler第一次被调用,将它们释还给程序使用。
  • 安装另一个new-handler。如果目前这个new-handler无法取得更多可用内存,或许它知道另外哪个new-handler有此能力。果真如此,目前这个new-handler就可以安装另外那个new-handler以替换自己(只要调用set_new_handler)。
  • 卸除new-handler,也就是将null指针传给set_new_handler。一旦没有安装任何new-handler,operator new会在内存分配不成功时抛出异常。
  • 抛出bad_alloc(或派生自bad_alloc)的异常。这样的异常不会被operator new捕捉,因此会被传播到内存索求处。
  • 不返回,通常调用abort或exit。

        这些选择让你在实现new-handler函数时拥有很大弹性。

        这时候你或许希望以不同的方式处理内存分配失败情况,你希望视被分配物属于哪个class而定:

class X {
    public:
    static void outOfMemory();
    ...
};
class Y {
    public:
    static void outOfMemory();
    ...
};
X *p1 = new X;  // 如果分配不成功,调用X::outOfMemory
Y *p2 = new Y;  // 如果分配不成功,调用Y::outOfMemory

        C++并不支持class专属之new-handlers,但其实也不需要。你可以自己实现出这种行为。只需令每个class提供自己的set_new_handler和operator new即可。其中set_new_handler使客户得以指定class专属的new-handler(就像标准的set_new_handler允许客户指定global new-handler),至于operator new则确保在分配class对象内存的过程中以class专属之new-handler替换global new-handler。

        现在,假设你打算处理Widget class的内存分配失败情况。首先你必须登录“当operator new无法为一个Widget对象分配足够内存时”调用的函数,所以你需要声明一个类型为new_handler的static成员,用以指向class Widget的new-handler。

看起来像这样:

class Widget {
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);
private:
    static std::new_handler currentHandler;
};

        Static成员必须在class定义式之外被定义(除非它们是const而且是整数型,见条款2),所以需要这么写:

std::new_handler Widget::currentHandler = 0;  // 在class实现文件内初始化为null

        Widget内的set_new_handler函数会将它获得的指针存储起来,然后返回先前存储的指针,这也正是标准版set_new_handler的作为:

std::new_handler Widget::set_new_handler(std::new_handler p) throw()
{
    std::new_handler oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;
}

        最后,Widget的operator new做以下事情:

  1. 调用标准set_new_handler,告知Widget的错误处理函数。这会将Widget的new-handler安装为global new-handler。
  2. 调用global operator new,执行实际之内存分配。如果分配失败,global operator new会调用Widget的new-handler,因为那个函数才刚被安装为global new-handler。如果global operator new最终无法分配足够内存,会抛出一个bad_alloc异常。
  3. 如果global operator new能够分配足够一个Widget对象所用的内存,Widget的operator new会返回一个指针,指向分配所得。Widget析构函数会管理global new-handler,它会自动将Widget's operator new被调用前的那个global new-handler恢复回来。

        下面以C++代码再阐述一次。我将从资源处理类开始,那里面只有基础性RAII操作,在构造过程中获得一笔资源,并在析构过程中释还(见条款13):

class NewHandlerHolder {
public:
    explicit NewHandlerHolder(std::new_handler nh)  // 取得目前的new-handler
    : handler(nh) {}
    ~NewHandlerHolder()                             // 释放它
    { std::set_new_handler(handler); }
private:
    std::new_handler handler;                       // 记录下来
    NewHandlerHolder(const NewHandlerHolder&);      // 阻止copying(见条款14)
    NewHandlerHolder& operator=(const NewHandlerHolder&);
};

这就使得Widget's operator new的实现相当简单:

void* Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
    NewHandlerHolder h(std::set_new_handler(currentHandler));  // 安装Widget的new-handler,分配内存或抛出异常
    return ::operator new(size);                               // 恢复global new-handler.
}

Widget的客户应该类似这样使用其new-handling:

void outOfMem();                     // 函数声明。此函数在Widget对象分配失败时被调用
Widget::set_new_handler(outOfMem);   // 设定outOfMem为Widget的new-handling函数
Widget* pw1 = new Widget;            // 如果内存分配失败调用outOfMem
std::string* ps = new std::string;   // 如果内存分配失败调用global new-handling函数
Widget::set_new_handler(0);          // 设定Widget专属的new-handling函数为null
Widget* pw2 = new Widget;            // 如果内存分配失败,立即抛出异常(class Widget没有专属的new-handling函数)

请记住

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值