当前位置: 首页 > article >正文

Effective C++读书笔记——item52(如果编写了 placement new,就要编写 placement delete)

在 C++ 中,当编写自定义的 placement new 时,需要同时编写对应的 placement delete,并且要注意避免覆盖 new 和 delete 的常规版本,以防止内存泄漏和使用异常。下面结合代码详细介绍相关知识点。

1. placement new 和 placement delete 的基本概念

当使用 new 表达式创建对象时,会先调用 operator new 分配内存,再调用对象的构造函数。若构造函数抛出异常,C++ 运行时系统需撤销之前的内存分配,这就要求有对应的 operator delete 函数。

常规的 operator new 和 operator delete 匹配关系明确,但当 operator new 带有额外参数时,就形成了 placement new,此时需要有对应的 placement delete(带有相同额外参数)来处理构造函数抛出异常时的内存释放。

2. 内存泄漏问题及解决方法

如果 placement new 没有对应的 placement delete,当构造函数抛出异常时,运行时系统无法撤销内存分配,会导致内存泄漏。

#include <iostream>
#include <new>

class Widget {
public:
    // 自定义的 placement new
    static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc) {
        logStream << "Allocating memory for Widget" << std::endl;
        return ::operator new(size);
    }

    // 常规的 operator delete
    static void operator delete(void* pMemory) throw() {
        ::operator delete(pMemory);
    }

    // 缺少对应的 placement delete,会导致内存泄漏

    Widget() {
        // 模拟构造函数抛出异常
        throw std::bad_alloc();
    }
};

int main() {
    try {
        Widget* pw = new (std::cerr) Widget;
    } catch (const std::bad_alloc& e) {
        std::cerr << "Exception caught: " << e.what() << std::endl;
        // 由于缺少对应的 placement delete,内存泄漏
    }
    return 0;
}

为了解决这个问题,需要添加对应的 placement delete

#include <iostream>
#include <new>

class Widget {
public:
    // 自定义的 placement new
    static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc) {
        logStream << "Allocating memory for Widget" << std::endl;
        return ::operator new(size);
    }

    // 常规的 operator delete
    static void operator delete(void* pMemory) throw() {
        ::operator delete(pMemory);
    }

    // 对应的 placement delete
    static void operator delete(void* pMemory, std::ostream& logStream) throw() {
        logStream << "Deallocating memory for Widget" << std::endl;
        ::operator delete(pMemory);
    }

    Widget() {
        // 模拟构造函数抛出异常
        throw std::bad_alloc();
    }
};

int main() {
    try {
        Widget* pw = new (std::cerr) Widget;
    } catch (const std::bad_alloc& e) {
        std::cerr << "Exception caught: " << e.what() << std::endl;
        // 构造函数抛出异常时,对应的 placement delete 会被调用,避免内存泄漏
    }
    return 0;
}

需要注意的是,只有在调用 placement new 关联的构造函数时发生异常,placement delete 才会被调用;正常使用 delete 指针时,调用的是常规的 operator delete

3. 避免覆盖 new 和 delete 的常规版本

在类中声明自定义的 operator new 会覆盖全局的标准形式,包括常规的 newplacement new 和 nothrow new。为了避免这种情况,可以创建一个包含所有标准形式的基类,并使用 using 声明让派生类可见。

#include <iostream>
#include <new>

// 包含所有标准形式的基类
class StandardNewDeleteForms {
public:
    // 常规的 new/delete
    static void* operator new(std::size_t size) throw(std::bad_alloc) {
        return ::operator new(size);
    }
    static void operator delete(void* pMemory) throw() {
        ::operator delete(pMemory);
    }

    // placement new/delete
    static void* operator new(std::size_t size, void* ptr) throw() {
        return ::operator new(size, ptr);
    }
    static void operator delete(void* pMemory, void* ptr) throw() {
        return ::operator delete(pMemory, ptr);
    }

    // nothrow new/delete
    static void* operator new(std::size_t size, const std::nothrow_t& nt) throw() {
        return ::operator new(size, nt);
    }
    static void operator delete(void* pMemory, const std::nothrow_t&) throw() {
        ::operator delete(pMemory);
    }
};

class Widget : public StandardNewDeleteForms {
public:
    using StandardNewDeleteForms::operator new;
    using StandardNewDeleteForms::operator delete;

    // 自定义的 placement new
    static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc) {
        logStream << "Allocating memory for Widget" << std::endl;
        return ::operator new(size);
    }

    // 对应的 placement delete
    static void operator delete(void* pMemory, std::ostream& logStream) throw() {
        logStream << "Deallocating memory for Widget" << std::endl;
        ::operator delete(pMemory);
    }
};

int main() {
    // 使用常规的 new
    Widget* pw1 = new Widget;
    delete pw1;

    // 使用自定义的 placement new
    Widget* pw2 = new (std::cerr) Widget;
    try {
        delete pw2;
    } catch (...) {}

    // 使用 placement new 的标准形式
    char buffer[sizeof(Widget)];
    Widget* pw3 = new (buffer) Widget;
    pw3->~Widget();

    // 使用 nothrow new
    std::nothrow_t nt;
    Widget* pw4 = new (nt) Widget;
    if (pw4) {
        delete pw4;
    }

    return 0;
}

总结要点

  • 对应关系:编写 operator new 的 placement 版本时,必须同时编写 operator delete 的相应 placement 版本,以避免构造函数抛出异常时的内存泄漏。
  • 避免覆盖:声明 new 和 delete 的 placement 版本时,要确保不会无意中覆盖这些函数的常规版本,可以通过继承包含标准形式的基类并使用 using 声明来解决。

http://www.kler.cn/a/552808.html

相关文章:

  • Spring Security,servlet filter,和白名单之间的关系
  • 【前端ES】ECMAScript 2023 (ES14) 引入了多个新特性,简单介绍几个不为人知但却好用的方法
  • 【Python爬虫(14)】解锁Selenium:Python爬虫的得力助手
  • npm、yarn、pnpm 的异同及为何推荐 pnpm
  • DeepSeek AI 完全使用指南:从入门到精通
  • Node.js 版本与 npm 的关系及版本特性解析:从开源项目看演进
  • 腿足机器人之九- SLAM基础
  • 跳板机和堡垒机的区别
  • HDFS应用-后端存储cephfs-java-API
  • 论文阅读 DOES END-TO-END AUTONOMOUS DRIVING REALLY NEED PERCEPTION TASKS?
  • 上位机知识篇---与、或、移位操作(、|、>><<)
  • 工具包组件和内置Agent组件
  • C++编程,#include <iostream>详解,以及using namespace std;作用
  • Linux基础24-C语言之分支结构Ⅰ【入门级】
  • STM32 GPIO误触发问题全解析:从噪声干扰到电路设计优化
  • 基于springboot+vue的在线考试系统
  • 【自学笔记】机器学习基础知识点总览-持续更新
  • 30道Qt面试题(答案公布)
  • 开源多商户商城源码最新版_适配微信小程序+H5+APP+PC多端
  • cesium基础设置