在C++里如何释放内存的时候不调用对象的析构函数?

news2024/9/9 3:59:23

今天,看到一个有趣的面试题,问题是:在C++里如何释放内存的时候不调用对象的析构函数?

之所以有趣,是因为这个问题违反了C++中资源管理的RAII(资源获取即初始化),它要求资源的释放应当和对象的生命周期紧密相关。在正常情况下,当对象离开其作用域时,它的析构函数被调用,以释放它所管理的资源,比如内存、文件句柄或网络连接等。

然而,这个问题提出了一种特殊情况,在出于性能优化、特殊的内存管理策略,或是为了与低级操作系统功能或硬件直接交互的需求。在这些情况下,我们可能需要释放对象占用的内存,但又不希望执行其析构函数。

在C++中,如果真的需要这么做,有什么方法呢?我们一起来梳理看看。

placement new方式

可以通过使用 placement new 来在预先分配的内存块上构造对象,然后不显式调用它的析构函数。

#include <new> // 需要包含头文件new

char buffer[sizeof(MyClass)]; // 分配足够的内存来存放MyClass对象
MyClass* obj = new(buffer) MyClass(); // 在buffer上构造对象

// ... 使用obj

// 显式调用析构函数是这样的:
// obj->~MyClass();

// 如果你不调用析构函数,对象的生命周期将结束,
// 但是它的析构函数不会被执行。但是由于对象用的是栈上的内存,内存会正常释放。

使用 placement new 需要你非常明确地知道自己在做什么,因为这样做会绕过正常的构造和析构过程。这可能导致资源泄露、内存未正确释放或其他未定义行为。

MyClass* obj = new MyClass(); // 常规地分配对象
// ... 在这里使用obj
operator delete(obj); // 释放内存但不调用析构函数

placement new的chromium的封装

chromium里面对placement new的设计模式提供了一套模板支持,如下:

template <typename T>
class NoDestructor {
 public:
  // Not constexpr; just write static constexpr T x = ...; if the value should
  // be a constexpr.
  template <typename... Args>
  explicit NoDestructor(Args&&... args) {
    new (storage_) T(std::forward<Args>(args)...);
  }

  // Allows copy and move construction of the contained type, to allow
  // construction from an initializer list, e.g. for std::vector.
  explicit NoDestructor(const T& x) { new (storage_) T(x); }
  explicit NoDestructor(T&& x) { new (storage_) T(std::move(x)); }

  NoDestructor(const NoDestructor&) = delete;
  NoDestructor& operator=(const NoDestructor&) = delete;

  ~NoDestructor() = default;

  const T& operator*() const { return *get(); }
  T& operator*() { return *get(); }

  const T* operator->() const { return get(); }
  T* operator->() { return get(); }

  const T* get() const { return reinterpret_cast<const T*>(storage_); }
  T* get() { return reinterpret_cast<T*>(storage_); }

 private:
  alignas(T) char storage_[sizeof(T)];
};


//使用方法:
void foo() {
  // std::string析构函数不会被调用,即便出了foo的scope
  NoDestructor<std::string> s("Hello world!");
}

上述代码的细节说明:

  • new (storage_) T(x) 使用了 placement new 操作符。这个操作符的语法是 new (address) Type(arguments),它允许你在一个已经分配好的内存地址 address 上直接构造一个 Type 类型的对象。这个操作不会分配新的内存,而是使用你提供的内存地址。在这个例子中,storage_ 是一个足够大的字符数组,能够存放 T 类型的对象,而 alignas(T) 确保了这个数组的对齐方式与 T 类型相同。

  • T(x) 是调用 T 类型对象的复制构造函数,以 x 为参数来构造一个新的 T 实例。

NoDestructor 类的 storage_ 成员中直接构造一个 T 类型的对象。因为它使用了 placement new,所以不会为这个 T 对象分配新的堆内存,而是利用 storage_ 这块已经预留的栈内存。这也意味着 T 对象的析构函数不会在 NoDestructor 对象被销毁时自动调用,这正是 NoDestructor 的设计目的。

union方式

union类型的析构函数在执行body之后不会调用variant member对象的析构函数

#include <iostream>
template<class T>
union NoDestructor{
    T value;
    ~Forget(){}
};

struct A{
   ~A(){
      std::cout<<"destroy A\n";
   }
};

int main(){
  auto f =  NoDestructor<A>{A{}}; // 不会执行A的析构
  // f.value.~A(); // 需要手动调用析构, 否则不会析构
}


union 是一种特殊的类类型,它允许你在同一个内存地址存储不同的数据类型,但是一次只能使用其中一个成员。这意味着 union 的所有成员都共享同一块内存空间,所以其大小等于其最大成员的大小。

union 有一些限制,其中之一就是所有的成员函数必须是非虚(non-virtual)的。理解这一点需要知道虚函数和虚函数表(vtable)的工作原理。在C++中,当类有一个或多个虚函数时,编译器会为该类创建一个虚函数表。这个虚函数表是一个函数指针数组,用于支持动态绑定,也就是在运行时决定调用哪个函数。每个有虚函数的对象都会含有一个指向虚函数表的指针,通常称为vptr。在 union 的情况下,由于所有成员共享同一块内存空间,如果 union 允许虚函数存在,那么vptr的存储位置就会和 union 的其他成员发生冲突,导致不确定的行为。此外,由于 union 的成员可以是不同的数据类型,编译器也无法确定应该使用哪个成员的虚函数表。

正因为这些原因,C++标准规定 union 不能包含虚函数。所有的成员函数,包括构造函数和析构函数,都必须是非虚的。这样就保证了 union 成员之间不会发生内存覆盖,同时也避免了动态绑定相关的复杂性。

在C++11及以后的版本中,union 可以包含非静态数据成员的构造函数和析构函数,但是仍然不能包含虚函数。如果 union 包含一个或多个非平凡的成员(比如包含自己的构造函数或析构函数的类类型成员),那么你需要负责正确地构造和析构这些成员,因为 union 不会自动为你做这些事情。

利用union的这个特性,就能轻松实现“释放内存的时候不调用对象的析构函数”。

但是,在使用union的时候,这个特性反而是一个坑,需要小心处理。一般来说,需要手动判断哪个成员是有效的,并显式地调用该成员的析构函数。类似这样:

union U {
    Type1 member1;
    Type2 member2;
    // ...
    
    ~U() {
        switch (active_member) {
            case Member1:
                member1.~Type1();  // 显式调用析构函数
                break;
            case Member2:
                member2.~Type2();  // 显式调用析构函数
                break;
            // ...
        }
    }
};

jmp 方式

直接通过longjmp,跳出作用域,避免析构函数调用:

#include <setjmp.h>
int main()
{
	jmp_buf buf {};
    if (setjmp(buf) == 0) {
	    string s(p); // 对象s不会析构
         longjmp(buf, 1);   
    }
}

不过通过longjmp没有很好的封装形式,语义上也过于隐晦,因此不常用于这个场景。

结语

这个面试题既有趣也有深度,它提供了一个探讨C++语言内存和资源管理机制的机会,同时考察面试者对C++底层细节的了解程度。然而,在实际的软件开发中,绝大多数情况下都应该遵循RAII原则,让析构函数自动管理资源的释放。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【速记!】3DMAX的50个常用快捷键

分享一组基本的3dMax动画和建模快捷键&#xff0c;以用于你的建筑项目。 3dMax是创建三维模型和动画的设计师中流行的软件。它用于建筑、电子游戏或其他需要高清晰度和高精度图形的视觉项目&#xff0c;是视觉艺术家寻找新工具的理想伴侣&#xff0c;这些工具可以帮助他们详细…

Vue3实战案例 知识点全面 推荐收藏 超详细 及附知识点解读

最近经常用到vue中的一些常用知识点&#xff0c;打算系统性的对 vue3 知识点进行总结&#xff0c;方便自己查看&#xff0c;另外也供正在学习 vue3 的同学参考&#xff0c;本案例基本包含 Vue3所有的基本知识点&#xff0c;欢迎参考&#xff0c;有问题评论区留言&#xff0c;谢…

Linux基本功能

Linux 操作系统&#xff0c;作为开源社区的明星之一&#xff0c;以其稳定性、安全性和灵活性在全球范围内得到广泛应用。 1. 多用户和多任务支持 Linux 是一个真正的多用户系统&#xff0c;允许多个用户同时登录并在同一时间内运行多个程序。每个用户拥有自己的账户和权限&…

每日OJ_牛客HJ86 求最大连续bit数

目录 牛客HJ86 求最大连续bit数 解析代码 牛客HJ86 求最大连续bit数 求最大连续bit数_牛客题霸_牛客网 解析代码 根据位运算&#xff0c;获取每一位的二进制值。获取第i位的值&#xff1a; (n >> i) & 1或者 n & (1 << i)。如果1连续&#xff0c;则计数…

Redis 安装和数据类型

Redis 安装和数据类型 一、Redis 1、Redis概念 redis 缓存中间件&#xff1a;缓存数据库 nginx web服务 php 转发动态请求 tomcat web页面&#xff0c;也可以转发动态请求 springboot 自带tomcat 数据库不支持高并发&#xff0c;一旦访问量激增&#xff0c;数据库很快就…

网工内推 | 合资公司、上市公司数据库工程师,OCP/OCM认证优先,双休

01 欣旺达电子股份有限公司 &#x1f537;招聘岗位&#xff1a;数据库管理高级工程师 &#x1f537;岗位职责&#xff1a; 1、负责数据库规划、管理、调优工作&#xff1b; 2、负责数据库应急预案制定、应急预案维护和应急支持&#xff1b; 3、负责数据库异常处理&#xff…

Unity UGUI 之 事件触发器

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 本文在发布时间选用unity 2022.3.8稳定版本&#xff0c;请注意分别 1.什么是UI事件触发器…

linux安装jdk和jps(为rocketMq准备)

20240730 一、安装rocketMq之前的准备工作1. 安装jkd&#xff08;这里以1.8为例子&#xff09;1.1 下载jdk1.81.2 上传到linux&#xff08;拖拽&#xff09;1.3 解压1.4 配置环境变量1.5 使配置文件生效1.6 验证结果 2. JPS2.1 解决 一、安装rocketMq之前的准备工作 1. 安装jk…

angular入门基础教程(十)管道即过滤器

管道 何为管道&#xff0c;ng 翻译的真烂&#xff0c;但是听多了你就理解了&#xff0c;类似于 vue2 中的过滤器&#xff0c;过滤器在 vue3 中已经废弃 从common包里面引入&#xff0c;并注册 import { Component, inject } from "angular/core"; import { UpperC…

C# 调用Webservice接口接受数据测试

1.http://t.csdnimg.cn/96m2g 此链接提供测试代码&#xff1b; 2.http://t.csdnimg.cn/64iCC 此链接提供测试接口&#xff1b; 关于Webservice的基础部分不做赘述&#xff0c;下面贴上我的测试代码&#xff08;属于动态调用Webservice&#xff09;&#xff1a; 1&#xff…

Appium自动化测试 ------ 常见模拟操作!

Appium自动化测试中的常见模拟操作涵盖了多种用户交互行为&#xff0c;这些操作对于自动化测试框架来说至关重要&#xff0c;因为它们能够模拟真实用户的使用场景&#xff0c;从而验证应用程序的功能和稳定性。 以下是一些Appium自动化测试中常见的模拟操作&#xff1a; 基本操…

XPathParser类

XPathParser类是mybatis对 javax.xml.xpath.XPath的包装类。 接下来我们来看下XPathParser类的结构 1、属性 // 存放读取到的整个XML文档private final Document document;// 是否开启验证private boolean validation;// 自定义的DTD约束文件实体解析器&#xff0c;与valida…

JavaSE面向对象进阶

static 介绍 static表示静态&#xff0c;是Java中的一个修饰符可以修饰成员方法、成员变量 被static修饰的成员变量&#xff0c;叫做静态变量被static修饰的成员方法&#xff0c;叫做静态方法 静态变量 特点&#xff1a;被该类所有对象共享 调用方式&#xff1a; 类名调用&am…

关于@Async

Spring Boot 2.x 开始&#xff0c;默认情况下&#xff0c;Spring AOP 使用 CGLIB 代理 Async不能在同一个类中直接调用 关于在控制器不能使用Async 并不是因为SpringBoot2以前使用JDK代理 因为JDK代理需要类实现接口,控制器没有实现接口等原因 真正原因是 Async 不能…

windows@powershell@任务计划@自动任务计划@taskschd.msc.md

文章目录 使用任务计划windows中的任务计划任务计划命令行程序开发windows 应用中相关api传统图形界面FAQ schtasks 命令常见用法创建计划任务删除计划任务查询计划任务修改计划任务运行计划任务 PowerShell ScheduledTasks常用 cmdlet 简介1. Get-ScheduledTask2. Register-Sc…

手动在ubuntu上搭建一个nginx,并安装证书的最简化完整过程

背景&#xff1a;由于想做个测试&#xff1a;即IP为A的服务器&#xff0c;绑定完域名X后&#xff0c;如果再绑定域名Y&#xff0c;能不能被访问到。&#xff08;假设对A不做绑定域名的设置&#xff09; 这个问题的来源&#xff0c;见上一篇文章&#xff1a;《云服务器被非法域名…

kaggle使用api下载数据集

背景 kaggle通过api并配置代理下载数据集datasets 步骤 获取api key 登录kaggle&#xff0c;点个人资料&#xff0c;获取到自己的api key 创建好的key会自动下载 将key放至家目录下的kaggle.json文件中 我这里是windows的administrator用户。 装包 我用了虚拟环境 pip …

021.自定义指纹浏览器编译-修改ClientRects指纹

一、什么是ClientRects指纹 ClientRects指纹获取的核心方法是DOM元素方法getClientRects()​ 。getClientRects()​ 可以返回一个元素的所有 CSS 边界框&#xff08;ClientRect对象数组&#xff09;&#xff0c;包括其大小、位置等信息。每个边界框由其左上角的 x, y 坐标和宽…

基于YOLOv10深度学习的商品条形码智能检测与识别系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标检测

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

小程序、H5、APP中的微信支付概述和实战总结

最近开发的一个微信小程序的项目结束了&#xff0c;里面用到了支付相关的api&#xff0c;借着项目总结一下小程序各种场景支付的逻辑。 1. 微信支付概述 1.1 微信支付的重要性 微信支付作为中国领先的移动支付方式之一&#xff0c;其便捷性、安全性以及广泛的用户基础使其成为…