Effective C++ 规则51:编写 new 和 delete 时需固守常规

1、背景

在 C++ 中,如果你需要为类自定义 new 和 delete,必须遵循一些约定和规则,以确保内存管理的一致性、可维护性和安全性。当我们使用 new 和 delete 操作时,C++ 编译器会:

  • 调用全局或类特定的 operator new 来分配内存。
  • 调用构造函数(new)或析构函数(delete)。
  • 如果需要,调用全局或类特定的 operator delete 来释放内存。
    通常,类的内存管理行为依赖于全局版本的 operator new 和 operator delete,但在某些场景下,你可能需要为类定义自定义的版本。

2、自定义new和delete的基本规则

2.1、成对出现

如果为类定义了自定义的 operator new,则必须同时定义对应的 operator delete。

cpp 复制代码
#include <iostream>
#include <cstdlib>

class Widget {
public:
    static void* operator new(size_t size) {
        std::cout << "Custom operator new: Allocating " << size << " bytes" << std::endl;
        return std::malloc(size);
    }

    static void operator delete(void* ptr) noexcept {
        std::cout << "Custom operator delete: Freeing memory" << std::endl;
        std::free(ptr);
    }
};

int main() {
    /*
    下面这句会执行两步:
    1、调用 operator new(size_t size) 为对象分配内存,在执行这一步时,会将sizeof(Widget)作为参数
    2、调用对象的构造函数在分配的内存上初始化对象。
    */
    Widget* w = new Widget;
    delete w;
    /*
    Custom operator new: Allocating 1 bytes
    Custom operator delete: Freeing memory
    */
    return 0;
}

2.2、匹配的内存分配和释放

  • 任何通过 operator new 分配的内存,必须使用对应的 operator delete 释放。
  • 避免跨越模块或库边界使用不同版本的 new 和 delete。

2.3、确保异常安全

自定义 operator new 应确保在分配失败时抛出 std::bad_alloc,而不是返回 nullptr。

cpp 复制代码
#include <new>
#include <iostream>

void* operator new(size_t size) {
    if (size == 0) size = 1; // 确保非零分配
    void* ptr = std::malloc(size);
    if (!ptr) throw std::bad_alloc(); // 分配失败时抛出异常
    return ptr;
}

void operator delete(void* ptr) noexcept {
    std::free(ptr);
}

2.4、定义 placement new 和 delete

C++ 提供了 placement new,允许你在已分配的内存上构造对象。如果你需要自定义 operator new,也应该定义对应的 placement operator delete。

cpp 复制代码
#include <iostream>

class Widget {
public:
    static void* operator new(size_t size, void* location) {
        std::cout << "Placement new called" << std::endl;
        return location;
    }

    static void operator delete(void* ptr, void* location) {
        std::cout << "Placement delete called" << std::endl;
        // 不释放内存,因为是 placement new
    }
};

int main() {
    char buffer[sizeof(Widget)];
    /*
    下面这句会执行两步:
    1、调用 operator new(size_t size, void* location),在执行这一步时,会将sizeof(Widget)作为第一个参数,buffer作为第2个参数
    2、调用对象的构造函数在分配的内存上初始化对象。
    */
    Widget* w = new (buffer) Widget;
    w->~Widget(); // 显式调用析构函数,输出Placement new called
    return 0;
}

3、特殊场景

  • 定制小对象分配器,对于需要频繁分配和释放的小对象,可以实现更高效的内存池,这样做可以优化的原因是,提前做好了分配内存的这一步,在获取到内存后,只需要再调用构造函数就可以了。
cpp 复制代码
#include <iostream>
#include <vector>

class SmallObjectAllocator {
private:
    std::vector<void*> freeList;
    size_t objectSize;

public:
    SmallObjectAllocator(size_t objSize) : objectSize(objSize) {}

    void* allocate() {
        if (freeList.empty()) {
            return std::malloc(objectSize);
        } else {
            void* ptr = freeList.back();
            freeList.pop_back();
            return ptr;
        }
    }

    void deallocate(void* ptr) {
        freeList.push_back(ptr);
    }
};

class Widget {
public:
    static SmallObjectAllocator allocator;

    static void* operator new(size_t size) {
        return allocator.allocate();
    }

    static void operator delete(void* ptr) {
        allocator.deallocate(ptr);
    }
};

SmallObjectAllocator Widget::allocator(sizeof(Widget));

int main() {
    Widget* w1 = new Widget;
    Widget* w2 = new Widget;
    delete w1;
    delete w2;
    return 0;
}
  • 优点,减少小对象分配的开销,提升内存分配性能。

3、总结

在编写 operator new 和 operator delete 时,应遵循以下关键点:

  • 成对定义 new 和 delete,包括 placement 版本。
  • 确保异常安全,分配失败时抛出 std::bad_alloc。
  • 遵循匹配的内存分配和释放规则,避免跨模块不一致。
  • 在需要优化性能时,可实现自定义的内存池或分配器。
相关推荐
今儿敲了吗16 分钟前
19| 海底高铁
c++·笔记·学习·算法
小冻梨66624 分钟前
ABC444 C - Atcoder Riko题解
c++·算法·双指针
星火开发设计29 分钟前
模板特化:为特定类型定制模板实现
java·开发语言·前端·c++·知识
凤年徐30 分钟前
优选算法——双指针专题 3.快乐数 4.盛水最多的容器
开发语言·数据结构·c++·算法
fpcc37 分钟前
并行编程实战—CUDA编程的占用率
c++·cuda
小亮✿1 小时前
算法—并查集
数据结构·c++·算法
王老师青少年编程1 小时前
2021年信奥赛C++提高组csp-s初赛真题及答案解析(阅读程序第3题)
c++·题解·真题·初赛·信奥赛·csp-s·提高组
colicode1 小时前
Objective-C语音验证码接口API示例代码:老版iOS应用接入语音验证教程
前端·c++·ios·前端框架·objective-c
犽戾武1 小时前
在 Quest 上用 OpenXR + MediaCodec + OES 外部纹理做一个“低延迟视频面板”(48小时的编码复盘)
linux·c++·嵌入式硬件·vr
犽戾武1 小时前
准备工作:OpenXR Sample 示例工程“去掉 UI 渲染”& RK3588→Windows 低延迟 UDP 视频链路
linux·c++·ubuntu·vr