[笔记]Effective C++改善程序与设计的55个具体做法_第八章 定制new和delete

条款 49:了解 new - handler 的行为。

当operator new抛出异常反应一个未满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的new-handler。设置错误处理的方式是调用set_new_handler。set_new_handler的标准库信息:

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

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

使用示例:

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

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

一个设计良好的new-handler函数必须做如下事情:

  • 让更多内存可被使用

书中给出的一个方式是程序一开始执行就分配一大块内存,而后当new-handler第一次被调用,将它们释还给程序使用。

还可以尝试如下方式:

通过释放当前进程中暂时不需要的内存资源(如缓存数据、临时对象、冗余容器等),为后续的内存分配腾出空间。

具体做法:清理全局 / 静态缓存(如std::vector::clear()+shrink_to_fit())、释放不常用的大型对象、关闭不必要的资源句柄等。

关键:释放的内存必须是可被operator new重新分配的(即归还给系统或进程的内存池)。

  • 安装另一个new-handler

若当前new_handler无法释放足够内存(如自身逻辑已穷尽),可替换为另一个更适合的处理函数(例如更激进的内存释放策略)。

目的:让下一次内存分配失败时,由新的new_handler尝试补救,增加分配成功的可能性。

示例:从 “温和释放缓存” 切换到 “释放所有非核心数据” 的处理函数。

  • 卸除new-handler

通过std::set_new_handler(nullptr)将处理函数设为空,意味着后续内存分配失败时不再调用任何new_handler,直接由operator new抛出std::bad_alloc异常。

适用场景:当前new_handler已确认无法释放内存,且无其他有效处理函数可用,此时应终止补救流程,交由上层代码处理异常。

  • 抛出bad_alloc(或派生自bad_alloc)的异常

new_handler可以主动抛出std::bad_alloc或其派生类异常(需保证异常可被operator new捕获)。此时operator new会终止分配尝试,并将异常传递给new的调用者。

注意:若抛出非bad_alloc家族的异常,会导致operator new无法处理,最终调用std::terminate终止程序。

  • 不返回

通过调用std::abort()std::exit()等函数直接终止程序,避免在内存严重不足的状态下继续运行(可能导致数据损坏、逻辑混乱等更严重问题)。

适用场景:内存分配失败属于 “不可恢复的致命错误”(如嵌入式系统内存耗尽、核心服务内存不足),此时终止程序是更安全的选择。

书中还描述了如何定制类对应的new_handler,暂时用不上,先略过。

条款 50:了解 new 和 delete 的合理替换时机。

替换编译器提供的operator new和operator delete的常见理由:

  • 用来检测运用上的错误

通过额外的内存空间标记监测是否被踩等场景。

  • 为了强化效能

编译器指定的new和delete过于中庸,因为考虑到普适性,在特定场景下不一定是最优解。

  • 为了收集使用上的统计数据

自定义便于收集不同维度的内存使用情况。

下面是一段定制new的实现实例(有些小错误)

static const int signature = 0xDEADBEEF;
typedef unsigned char Byte;

// 这段代码还有若干小错误
void* operator new(std::size_t size) throw(std::bad_alloc)
{
    using namespace std;
    size_t realSize = size + 2 * sizeof(int); // 增加大小使能够塞入两个signatures

    void *pMem = malloc(realSize);
    if (!pMem) throw bad_alloc();

    // 将signature写入内存的最前段落和最后段落
    *(static_cast<int*>(pMem)) = signature;
    *(reinterpret_cast<int*>(static_cast<Byte*>(pMem) + realSize - sizeof(int))) = signature;

    // 返回指针,指向恰位于第一个signature之后的内存位置
    return static_cast<Byte*>(pMem) + sizeof(int);

}

上述示例没有考虑齐位问题,尽管有很多理由重写new和delete但是仍然不推荐。

条款 51:编写 new 和 delete 时需固守常规。

这里只需要记住如下原则:

  • operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler.它也应该有能力处理0 bytes申请。Class专属版本则还应该处理"比正确大小更大的(错误)申请"。
  • operator delete应该在收到null指针时不做任何事。Class专属版本则还应该处理比正确大小更大的(错误)申请(通过监测是否是本Class大小的申请)。

条款 52:写了 placement new 也要写 placement delete。

带参场景的new和delete实现。先可以做了解。

  • 当你写一个placement operator new,请确定也写出了对应的placement operator delete.如果没有这样做,你的程序可能会发生隐微而时断时续的内存泄露。
  • 当你声明placement new和placement delet,请确定不要无意识(非故意)地遮掩了它们的正常版本。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇