8Effective C++(第3版)

定制new和delete

内存管理

operator newplacement new内存池对齐

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第8章:定制new和delete

一、导读

1.1 本章学习目标

第8章讲解如何定制 C++ 的内存分配机制:自定义 operator new/delete、内存池、对齐等。

本章核心学习目标:

  • 理解 operator new/delete 的工作机制
  • 掌握自定义内存分配器的实现
  • 理解内存池的设计
  • 掌握对齐(alignment)的处理

1.2 为什么本章如此重要

默认的内存分配器不一定适合所有场景。游戏引擎、嵌入式系统等需要定制内存管理以获得更好的性能和可控性。

1.3 核心问题引导

operator new 和 malloc 有什么区别?

内存池如何提高分配效率?

如何处理内存对齐?


二、核心概念详解

2.1 operator new 的工作流程

new Widget(42) 的执行步骤:
1. 调用 operator new(sizeof(Widget)) 分配原始内存
2. 在分配的内存上调用 Widget 的构造函数
3. 返回指向对象的指针

delete ptr 的执行步骤:
1. 调用 ptr 指向对象的析构函数
2. 调用 operator delete(ptr) 释放内存

operator new 失败处理:
- 默认抛出 std::bad_alloc
- 可以安装 new_handler: std::set_new_handler(handler)
- 可以指定 nothrow: new (std::nothrow) Widget

2.2 自定义 operator new/delete

cpp
class Widget {
public:
    // 类级别的 operator new
    static void* operator new(std::size_t size) {
        std::cout << "Custom new: " << size << " bytes\n";
        void* ptr = std::malloc(size);
        if (!ptr) throw std::bad_alloc();
        return ptr;
    }
    
    // 类级别的 operator delete
    static void operator delete(void* ptr) noexcept {
        std::cout << "Custom delete\n";
        std::free(ptr);
    }
    
    // C++14: 带大小的 delete
    static void operator delete(void* ptr, std::size_t size) noexcept {
        std::cout << "Custom delete (sized): " << size << " bytes\n";
        std::free(ptr);
    }
};

auto w = new Widget();  // 调用自定义 operator new
delete w;               // 调用自定义 operator delete

2.3 内存池

cpp
template<typename T, std::size_t BlockSize = 4096>
class MemoryPool {
    struct Node {
        Node* next;
    };
    
    union Slot {
        Node node;
        char data[sizeof(T)];
    };
    
    Node* freeList = nullptr;
    std::vector<Slot*> blocks;
    
    void allocateBlock() {
        std::size_t numSlots = BlockSize / sizeof(Slot);
        Slot* newBlock = static_cast<Slot*>(std::malloc(BlockSize));
        blocks.push_back(newBlock);
        
        for (std::size_t i = 0; i < numSlots - 1; ++i) {
            newBlock[i].node.next = &newBlock[i + 1].node;
        }
        newBlock[numSlots - 1].node.next = freeList;
        freeList = &newBlock[0].node;
    }
    
public:
    void* allocate(std::size_t size) {
        if (!freeList) allocateBlock();
        Node* node = freeList;
        freeList = freeList->next;
        return node;
    }
    
    void deallocate(void* ptr) {
        Node* node = static_cast<Node*>(ptr);
        node->next = freeList;
        freeList = node;
    }
    
    ~MemoryPool() {
        for (auto block : blocks) std::free(block);
    }
};

// 使用:
MemoryPool<Widget> pool;
Widget* w = new (pool.allocate(sizeof(Widget))) Widget();
w->~Widget();
pool.deallocate(w);

三、实战案例

3.1 案例一:内存池性能对比

cpp
#include <chrono>

void benchmark() {
    const int N = 1000000;
    
    // 标准 new/delete
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < N; ++i) {
        Widget* w = new Widget();
        delete w;
    }
    auto standard_time = std::chrono::high_resolution_clock::now() - start;
    
    // 内存池
    MemoryPool<Widget> pool;
    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < N; ++i) {
        Widget* w = static_cast<Widget*>(pool.allocate(sizeof(Widget)));
        pool.deallocate(w);
    }
    auto pool_time = std::chrono::high_resolution_clock::now() - start;
    
    std::cout << "Standard: " 
              << std::chrono::duration_cast<std::chrono::milliseconds>(standard_time).count() 
              << " ms\n";
    std::cout << "Pool: " 
              << std::chrono::duration_cast<std::chrono::milliseconds>(pool_time).count() 
              << " ms\n";
}
// 内存池通常比标准 new/delete 快 5-10 倍

四、本章小结

operator new/delete 是内存分配的底层接口,可以类级别或全局级别定制

内存池通过预分配和复用内存块,大幅提高分配效率

对齐影响内存访问效率,需要正确处理


五、思考与练习

思考题

内存池为什么比 malloc 快?

什么时候应该自定义 operator new?

编程练习

实现一个线程安全的内存池。

实现一个带对齐支持的内存分配器。


8/9定制ne
0%