浅谈重载new操作符

2023-05-16

new是C++里非常重要的一个关键词,用于申请内存、初始化对象。俗话说“有借有还再借不难”,通过new向操作系统“借”到的内存用完后必然要“还”回去,所以对应地还有一个delete操作符与new共同管理内存,delete的作用是析构对象、释放内存。

new有什么作用?

  • 申请内存
  • 初始化对象

说到内存管理,有些同学会想到C标准库函数malloc()free()。C++是C语言的延续,那么C++一定可以丝滑地使用这两个标准库函数管理内存,那为什么还要提供关键词newdelete呢?

我们申请到内存后一般都会先做初始化,关键词new相当于用一条语句做两件事——申请内存并初始化。考虑类Test

struct Test {
    ...
};

使用new生成一个Test的对象

Test* test = new Test(...);

相当于malloc()申请内存后再进行初始化

// void initTest(Test* test);

void* testBuff = malloc(sizeof(Test));
initTest((Test*)testBuff);

同理,delete也是一条语句做了两件事——先析构对象释放相关资源,然后再释放内存。

new和malloc有什么区别?

malloc()free()是函数,只用于管理内存。

newdelete是操作符,newdelete用于管理内存,还有其他职责——new初始化对象,delete析构对象(清理资源)。

函数和操作符的使用方式存在很大差异。另外,编译后函数调用对应一条函数调用指令,操作符对应一条或多条其他汇编指令。

new有哪些用法?

  • plain new
  • operator new
  • placement new

plain newnew的常见用法,用于生成对象。会申请内存并调用构造函数

Test* test = new Test(...);

operator new用于申请内存,作用与malloc()一样,实际上是函数调用。只会申请内存,不会调用构造函数

void* buff = operator new(sizeof(Test));

placement new用于调用构造函数初始化指定内存。只会调用构造函数,不会申请内存

// void* testBuff;

Test* test = new (testBuff)Test(...);

实际上只要观察Test* test = new Test(...);对应的汇编结果就能发现plain new用法包含了两步——申请内存、调用构造函数

...
mov     edi, 4
call    operator new(unsigned long)
mov     rbx, rax
mov     rdi, rbx
call    Test::Test() [complete object constructor]
mov     QWORD PTR [rbp-24], rbx
...

第3行指令表示调用函数operator new(unsigned long)申请内存;第6行指令表示调用Test的构造函数初始化内存。所以

Test* test = new Test();

相当于

void* buff = operator new(sizeof(Test));
Test* test = new ((Test*)buff)Test;

如何重载new操作符?

重载new操作符实际上是重写函数operator new(...),对应的是申请内存的步骤,所以重载new操作符后只会影响plain new的内存分配,不影响调用构造函数。

重载new操作符的形式有两种——全局重载、局部重载。

全局重载就是直接重写全局函数operator new(...)

void* operator new(size_t size) {
    ...
}

从前面Test* test = new Test(...);对应的汇编结果就能看出operator new()是一个全局函数(汇编后函数符号没有任何改变,也没有加任何作用阈),全局重写new操作符后,所有类型的new操作都会调用自定义的operator new()申请内存。

局部重载就是在类中重写静态函数operator new(...)(即便不以static修饰,编译器也会自动转为静态函数)

struct Test {
    void* operator new(size_t size) throw() {
        ...
    }
};

局部重载new操作符后,Test* test = new Test(...);对应的汇编代码为

...
mov     edi, 4
call    Test::operator new(unsigned long)
mov     rbx, rax
mov     rdi, rbx
call    Test::Test() [complete object constructor]
mov     QWORD PTR [rbp-24], rbx
...

可以看到第3行的函数调用变为Test::operator new(unsigned long),增加了作用域Test,意味着自定义的operator new()只会对Test有效。

operator new和throw?

我们知道Test* test = new Test(...);实际上是两步——申请内存、调用构造函数,现在有一个问题,如果内存申请失败返回空指针,构造函数初始化成员函数时岂不是会出现访问空指针的问题?之前在项目上遇到过类似的问题,重载new操作符后构造函数报了空指针访问异常,代码如下

class Test {
private:
    int value;

public:
    Test() {
        printf("[Test] Constructor\n");
        value = 1;
    }

    void* operator new(size_t size) {
        printf("[Test] operator new\n");
        return NULL;    // 内存申请失败,返回空指针
    }
};

客户代码

int main() {
    printf("-------- start --------\n");
    Test* point = new Test();
    printf("[main] point is %p\n", point);
    printf("-------- done --------\n");
    return 0;
}

运行结果出现内存访问错误,错误的位置发生在Test的构造函数中

-------- start --------
[Test] operator new
[Test] Constructor
Segmentation fault: 11

new操作符申请内存失败后,在构造函数中出现了访问空指针的错误。这个问题看起来非常棘手,new操作符会先申请内存然后调用构造函数,这个流程是编译器决定的不能修改,那么要解决这个问题似乎只有两个思路——在使用new之前手动判断内存是否会申请成功;在构造函数中判断this指针是否为空。

用这两种方式解决都存在隐患——因为需要改动很多代码,而人总是会出错的。事实上,一个throw()声明(或者宏_NOEXCEPT)就可以解决这个问题。

关键字thow的是用来声明异常的,说明函数会抛出哪些类型的异常,显而易见throw()说明函数不会抛出任何类型的异常,Test类只要做以下改动即可

class Test {
...
    void* operator new(size_t size) throw() {   // 只比原来的代码多了 throw()
        ...
    }
...
};

运行结果

-------- start --------
[Test] operator new
[main] point is 0x0
-------- done --------

new操作符申请内存失败后未调用构造函数,不会出现访问空指针的问题。

为了一探究竟,我们比较一下加了thow()声明和没有throw()声明的汇编结果(右侧是无throw()的,左侧是有throw()的)

在这里插入图片描述

总的来说,加了throw()声明后会多两条汇编指令,用于在operator new的返回结果是空指针时不调用Test的构造函数。

在线工具

最后,分享一个在线汇编神器: https://godbolt.org/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

浅谈重载new操作符 的相关文章

随机推荐

  • 【React Native】app\build\intermediates\res\merged\debug\values-v24\values-v24.xml中错误

    昨天在项目中使用了 react native svg 库 xff0c 配置完成正在正常启动的时候 xff0c 却出现四个错误 xff0c 全部来源于app build intermediates res merged debug value
  • 【React Native】定位获取经纬度,当前城市等地址信息

    2019 8 21 使用内置对象 navigator 来获取经纬度信息 xff0c 参见 定位获取经纬度 xff0c 获取到经纬度等位置信息后需要用到第三方api位置解析 xff08 本文后半段 xff09 2019 7 20更新 xff1
  • 【React Native】定位获取经纬度

    RN 文档上的定位功能需要谷歌框架支持 xff0c 无疑带来了一些麻烦 github 上也有一些开源库 xff0c react native geolocation service等 但是这里还有一个更简便的位置获取 API 使用内置对象n
  • centos7系统下Python2.7升级到Python3.6踩的坑(yum失效,并非简单修改yum文件头)

    centos系统自带的Python2 7用的好好的 xff0c 我非手贱要去升级 xff0c 结果很严重 xff0c 正在运行服务器里面的yum崩了 xff0c 反复尝试了网上提到的几乎完全一致的解决方法 xff1a 将 usr bin y
  • 关于 Android Studio 4.0 创建新的activity和fragment 发现不存在

    1 在 app文件下面的 build gradle里面 注释以下代码
  • RAR文件格式-笔记

    RAR RAR 文件头 52 61 72 21 1A 07 00RAR 文件尾 C4 3D 7B 00 40 07 00 Rar 文件主要由标记块 xff0c 压缩文件头块 xff0c 文件头块 xff0c 结尾块组成 其每一块大致分为以下
  • pptv电话面试

    1 8种基本数据类型 2 String是基本数据类型吗 3 try return 1 catch return 2 finally return3 4 线程池 5 spring实现原理 6 s
  • Linux之systemd服务配置及自动重启

    Linux之systemd服务配置及自动重启 0 背景 在linux上开发时 xff0c 往往需要将自己的程序做成服务 xff0c 并且实现服务开机自动重启 xff0c 以及服务崩溃后自动重启功能 xff0c 本文就对该功能的实现做简单介绍
  • C++中类与对象的关系

    C 43 43 是一门面向对象的编程语言 xff0c 理解C 43 43 xff0c 首先要理解类 xff08 Class xff09 和对象 xff08 Object xff09 这两个概念 C 43 43 中的类 xff08 Class
  • 主定理的证明及应用举例

    主定理 主定理最早出现在 算法导论 中 xff0c 提供了分治方法带来的递归表达式的渐近复杂度分析 规模为n的问题通过分治 xff0c 得到a个规模为n b的问题 xff0c 每次递归带来的额外计算为c n d T n lt 61 aT n
  • Java设计模式 | 观察者模式解析与实战

    概述 观察者模式是一个使用率非常高的模式 xff0c 它最常用的地方是 GUI 系统 订阅 发布系统 这个模式的一个重要作用就是解耦 xff0c 将被观察者和观察者解耦 xff0c 使得它们之间的依赖性更小 xff0c 甚至做到毫无依赖 以
  • blob excel文件导出

    vue 项目中excel文件导出 xff1a exportData 点击方法名称 jjrExport this years then res 61 gt this years为请求参数 console log res const type
  • 知识管理——学习篇

    你的知识需要管理 田志刚 2009年11月 现在 xff0c 根据本书的理念 xff0c 你的使命不仅仅是获取该书的知识 xff08 获取什么 xff1f 他的前瞻性思考判断 xff0c 人家10年前有这种知识管理预见和意识 xff01 作
  • dependencyManagement_前进的火车_新浪博客

    dependencyManagement使用简介 Maven中的dependencyManagement元素提供了一种管理依赖版本号的方式 在dependencyManagement元素中声明所依赖的jar包的版本号等信息 xff0c 那么
  • 【Redis】Redis简介与基本特性

    简介 Redis 全称为 Remote DIctionary Server xff0c 本质上是一个 key value 存储系统 xff0c 属于跨平台的非关系型数据库 Redis 官方对它的定义是 xff1a Redis is an o
  • 【Java 集合类】Collections 类源码分析

    Collections 类源码分析 包路径 xff1a jdk1 8 0 111 rt jar java util Collections java Collections是JDK提供的工具类 xff0c 同样位于java util包中 它
  • 首个Adobe XD教程丨如何用Adobe Experience Design (XD)快速设计图标

    提示 xff1a 阅读本文前建议先从 码农与产品经理看过来 教你用Adobe Xd轻松做原型设计 文章入手 xff0c 会更加学度的掌握此建模神器的精髓http www zoomla cn blog techs 3080 shtml 逐浪小
  • 关于ubuntu下终端打开一闪就退出的一种情况

    终端ctrl 43 alt 43 t后终端一闪即过 首先你换一下访客进入ubuntu查看是否可以打开终端 可以 xff0c 请接着往下看 xff0c 否则抱歉 xff01 首先请你回想一下 xff0c 在这之前你对系统有没有做过什么更改 x
  • 如何转行游戏行业及我眼中的理想游戏开发团队

    前置内容 xff1a 很久没有写文章啦 xff0c 今天就来继续和大家聊一聊关于游戏制作的一些东西 今天主要聊两个方面 xff0c 一个是其他行业转游戏行业的一些注意事项以及需要具备的东西 xff1b 第二是和大家聊一聊我心目中理想的游戏开
  • 浅谈重载new操作符

    new是C 43 43 里非常重要的一个关键词 xff0c 用于申请内存 初始化对象 俗话说 有借有还再借不难 xff0c 通过new向操作系统 借 到的内存用完后必然要 还 回去 xff0c 所以对应地还有一个delete操作符与new共