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。

#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。

#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。

#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、特殊场景

  • 定制小对象分配器,对于需要频繁分配和释放的小对象,可以实现更高效的内存池,这样做可以优化的原因是,提前做好了分配内存的这一步,在获取到内存后,只需要再调用构造函数就可以了。
#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。
  • 遵循匹配的内存分配和释放规则,避免跨模块不一致。
  • 在需要优化性能时,可实现自定义的内存池或分配器。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/961999.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于聚类与相关性分析对马来西亚房价数据进行分析

碎碎念&#xff1a;由于最近太忙了&#xff0c;更新的比较慢&#xff0c;提前祝大家新春快乐&#xff0c;万事如意&#xff01;本数据集的下载地址&#xff0c;读者可以自行下载。 1.项目背景 本项目旨在对马来西亚房地产市场进行初步的数据分析&#xff0c;探索各州的房产市…

(2025 年最新)MacOS Redis Desktop Manager中文版下载,附详细图文

MacOS Redis Desktop Manager中文版下载 大家好&#xff0c;今天给大家带来一款非常实用的 Redis 可视化工具——Redis Desktop Manager&#xff08;简称 RDM&#xff09;。相信很多开发者都用过 Redis 数据库&#xff0c;但如果你想要更高效、更方便地管理 Redis 数据&#x…

智慧园区管理系统为企业提供高效运作与风险控制的智能化解决方案

内容概要 快鲸智慧园区管理系统&#xff0c;作为一款备受欢迎的智能化管理解决方案&#xff0c;致力于为企业提供高效的运作效率与风险控制优化。具体来说&#xff0c;这套系统非常适用于工业园、产业园、物流园、写字楼及公寓等多种园区和商办场所。它通过数字化与智能化的手…

C++中常用的排序方法之——冒泡排序

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///计算机爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于C中常用的排序方法之——冒泡排序的…

基础项目实战——3D赛车(c++)

目录 前言一、渲染引擎二、关闭事件三、梯形绘制四、轨道绘制五、边缘绘制六、草坪绘制七、前后移动八、左右移动​九、曲线轨道​十、课山坡轨道​十一、循环轨道​十二、背景展示​十三、引入速度​十四、物品绘制​十五、课数字路障​十六、分数展示​十七、重新生成​十八、…

深度学习指标可视化案例

TensorBoard 代码案例&#xff1a;from torch.utils.tensorboard import SummaryWriter import torch import torchvision from torchvision import datasets, transforms# 设置TensorBoard日志路径 writer SummaryWriter(runs/mnist)# 加载数据集 transform transforms.Comp…

AI时序预测: iTransformer算法代码深度解析

在之前的文章中&#xff0c;我对iTransformer的Paper进行了详细解析&#xff0c;具体文章如下&#xff1a; 文章链接&#xff1a;深度解析iTransformer&#xff1a;维度倒置与高效注意力机制的结合 今天&#xff0c;我将对iTransformer代码进行解析。回顾Paper&#xff0c;我…

Java内存模型 volatile 线程安全

目录 Java内存模型可见性例子和volatilevolatile如何保证可见性原子性与单例模式i非原子性 线程安全 Java内存模型 参考学习: Java Memory Model外文文档 CPU与内存&#xff0c;可参考&#xff1a;https://blog.csdn.net/qq_26437925/article/details/145303267 Java线程与内…

【FreeRTOS 教程 三】协程状态、优先级、实现及调度

目录 一、协程介绍&#xff1a; &#xff08;1&#xff09;协程的特点&#xff1a; &#xff08;2&#xff09;协程的优势&#xff1a; 二、协程状态&#xff1a; &#xff08;1&#xff09;协程状态说明&#xff1a; &#xff08;2&#xff09;协程状态图示&#xff1a;…

堆的存储(了解)

由于堆是⼀个完全⼆叉树&#xff0c;因此可以⽤⼀个数组来存储。&#xff08;如果不清楚大家可以回顾⼆叉树的存储&#xff08;上&#xff09;c文章里的顺序存储&#xff09; 结点下标为 i &#xff1a; 如果⽗存在&#xff0c;⽗下标为 i/2 &#xff1b; 如果左孩⼦存在&…

谭浩强C语言程序设计(3) 7章

1、递归实现N的阶乘 c复制 #include <cstdio> // 包含标准输入输出库// 计算n的阶乘 int total 0; // 定义全局变量total用于存储阶乘结果// 递归函数计算阶乘 int fac(int a){// 如果输入的数小于0&#xff0c;输出错误信息if (a < 0){printf("%d < 0,err…

WPF基础 | WPF 常用控件实战:Button、TextBox 等的基础应用

WPF基础 | WPF 常用控件实战&#xff1a;Button、TextBox 等的基础应用 一、前言二、Button 控件基础2.1 Button 的基本定义与显示2.2 按钮样式设置2.3 按钮大小与布局 三、Button 的交互功能3.1 点击事件处理3.2 鼠标悬停与离开效果3.3 按钮禁用与启用 四、TextBox 控件基础4.…

MATLAB的数据类型和各类数据类型转化示例

一、MATLAB的数据类型 在MATLAB中 &#xff0c;数据类型是非常重要的概念&#xff0c;因为它们决定了如何存储和操作数据。MATLAB支持数值型、字符型、字符串型、逻辑型、结构体、单元数组、数组和矩阵等多种数据类型。MATLAB 是一种动态类型语言&#xff0c;这意味着变量的数…

模型I/O

文章目录 什么是模型I/O模型I/O功能之输出解析器输出解析器的功能输出解析器的使用Pydantic JSON输出解析器结构化输出解析器 什么是模型I/O 模型I/O在所有LLM应用中&#xff0c;核心元素无疑都是模型本身。与模型进行有效的交互是实现高效、灵活和可扩展应用的关键。LangChain…

docker安装Redis:docker离线安装Redis、docker在线安装Redis、Redis镜像下载、Redis配置、Redis命令

一、镜像下载 1、在线下载 在一台能连外网的linux上执行docker镜像拉取命令 docker pull redis:7.4.0 2、离线包下载 两种方式&#xff1a; 方式一&#xff1a; -&#xff09;在一台能连外网的linux上安装docker执行第一步的命令下载镜像 -&#xff09;导出 # 导出镜像…

QT串口通信,实现单个温湿度传感器数据的采集

1、硬件设备 RS485中继器(一进二出),usb转485模块、电源等等 => 累计115元左右。 2、核心代码 #include "MainWindow.h" #include "ui_MainWindow.h"MainWindow::

android主题设置为..DarkActionBar.Bridge时自定义DatePicker选中日期颜色

安卓自定义DatePicker选中日期颜色 背景&#xff1a;解决方案&#xff1a;方案一&#xff1a;方案二&#xff1a;实践效果&#xff1a; 背景&#xff1a; 最近在尝试用原生安卓实现仿element-ui表单校验功能&#xff0c;其中的的选择日期涉及到安卓DatePicker组件的使用&#…

6.工厂模式(Factory Method)

定义 通过“对象创建” 模式绕开new&#xff0c;来避免对象创建&#xff08;new&#xff09;过程中所导致的紧耦合&#xff08;依赖具体类&#xff09;&#xff0c;从而支持对象创建的稳定。它是接口抽象之后的第一步工作。 动机 在软件系统中&#xff0c;经常面临着创建对象…

Java CAS操作

通过前面的学习认识到了CPU缓存&#xff0c;Java内存模型&#xff0c;以及线程安全的原子、可见、顺序三大特性。本文则重点认识CAS操作&#xff0c;这是Java并发编程常见的一个操作&#xff0c;AbstractQueuedSynchronizer基于此操作提供了丰富的同步器和各种锁。 CAS&#x…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.25 视觉风暴:NumPy驱动数据可视化

1.25 视觉风暴&#xff1a;NumPy驱动数据可视化 目录 #mermaid-svg-i3nKPm64ZuQ9UcNI {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-i3nKPm64ZuQ9UcNI .error-icon{fill:#552222;}#mermaid-svg-i3nKPm64ZuQ9UcNI …