C++中运算符new的深入讲解

news/2024/12/2 10:52:20/

目录

  • 一 new运算符语法
  • 二 new 如何工作(理解运算符new 和函数operator new)
  • 三 operator new函数
  • 四 一个综合性的例子
  • 五 使用new可能导致的内存泄漏

写这篇文章的原因是:有一次,我见到了类似下面的代码,我感到很惊奇,从来没见到new的这种用法,后来查看MSDN,发现一个常用的new运算符居然有这么多语法规定,甚是感慨!所以记录在此,日后备查!

 Blanks *pBlanks = new(0xa5) Blanks;

一 new运算符语法

new运算符负责从堆内存区分配对象或者对象数组的内存空间,返回一个指向对象的非0指针。其语法为

[::] new [placement] new-type-name [new-initializer]
[::] new [placement] ( type-name ) [new-initializer]

其中:
placement:占位符,当重写operator new时,提供了一个传递额外参数的方式;占位符可以包含多个参数。
type-name:分配内存的类型,可以是内置的或者类类型。
initializer:对象的初始化值。不能用于数组。只有类具有默认构造函数的时候,new 运算符才会创建这个类的数组。
上述就是new操作符的基本语法,然后先把它放在一边。看一下操作符new如何工作。

二 new 如何工作(理解运算符new 和函数operator new)

分配运算表达式(即包含new操作符的表达式)会做三件事情:

  1. 当分配一个或多个对象时,定位并保留内存空间。这个阶段完成后,就分配了正确数量的内存空间,但这还不是一个对象。
  2. 初始化对象。一旦初始化完成,在内存空间就有了足够的信息,使其成为一个对象。
  3. 返回一个继承自此对象的指针。程序可以通过这个指针访问新建的对象。

new运算符会调用函数operator new. 对于任意类型的数组,非class, struct,或者union类型的对象,编译器会调用全局函数——::operator new——来分配内存。类类型的对象可以定义他们自己的operator new静态成员函数。
在代码中,当用new运算符为类类型type的对象分配内存时,编译器会调用 type::operator new( sizeof( type ) )成员函数;当这个类没有自定义的operator new运算符时,编译器会调用::operator new(sizeof(type))全局函数。请注意,使用new运算符编译时,编译器会自动计算类的大小后传递给函数operator new 。这样,new 运算符能够给对象分配正确数量的内存。
new语法中的一个选项允许指定占位符placement中的参数。placement参数仅仅允许使用在类中自定义的operator new实现中;它允许传递给opeator new额外的信息。例如下面的表达式:

T *TObject = new ( 0x0040 ) T;

当类中具有自定义的new操作符时会被编译为

T *TObject = T::operator new( sizeof( T ), 0x0040 ); //请注意,T::operator new的第一个参数是编译器自动添加的

否则被编译为

T *TObject = ::operator new( sizeof( T ), 0x0040 ); //请注意,::operator new的第一个参数是编译器自动添加的

请注意,尽管上述例子中在placement字段中只有一个参数,但实际上可以传递给operator new的参数没有限制。
即便类类型中有自定义的operator new,全局的操作符依然可以通过以下方式使用

T *TObject =::new TObject;

作用域解析运算符(::)强制使用全局的new操作符。

三 operator new函数

上面说过了,new运算符会调用operator new函数,而根据作用域不同,operator new分为以下两种类型:

OperatorScope
::operator newGlobal
class-name::operator newClass

operator new函数的第一个参数类型必须是size_t(在STDDEF.H中定义),然后返回类型永远是void *.
当new操作符用在分配内置类型、无自定义operator new成员函数的类类型时、任意类型的数组时 ,编译器会调用全局的operator new函数。
当new 操作符用在分配一个有自定义operator new(静态)成员函数的类类型时 ,编译器会调用类自己的自定义operator new 函数。
类自定义operator new 函数是一个静态成员(因此,不可能是virtual了),在分配这个类对象的内存时,会隐藏全局的operator new 。考虑下面的例子。

// spec1_the_operator_new_function1.cpp
#include <malloc.h>
#include <memory.h>class Blanks
{
public:Blanks(){}void *operator new( size_t stAllocateBlock, char chInit );
};
void *Blanks::operator new( size_t stAllocateBlock, char chInit )
{void *pvTemp = malloc( stAllocateBlock );if( pvTemp != 0 )memset( pvTemp, chInit, stAllocateBlock );return pvTemp;
}
// For discrete objects of type Blanks, the global operator new function
// is hidden. Therefore, the following code allocates an object of type
// Blanks and initializes it to 0xa5
int main()
{Blanks *a5 = new(0xa5) Blanks;return a5 != 0;
}

new括号中的实参值0xa5传递给Blanks::operator new的chInit 参数。全局的operator new函数这时候被隐藏了,所以下面的代码会报错:

Blanks *SomeBlanks = new Blanks;	//error,参数错误,缺一个参数

在Visual C++ 5.0和更早的版本,用new 运算符分配非类类型和 所有数组(不管数组元素是否为类类型)均是调用全局的operator new函数。
从Visual C++ 5.0以后,编译器支持了成员数组new和delete函数

// spec1_the_operator_new_function2.cpp
class MyClass
{
public:void * operator new[] (size_t){return 0;}void   operator delete[] (void*){}
};int main() 
{MyClass *pMyClass = new MyClass[5];delete [] pMyClass;
}

四 一个综合性的例子

在下面的例程中,你会理解如何给operator new传递参数、如何解除类对全局运算符new的屏蔽、new运算符如何初始化对象等等知识点。

#include <malloc.h>
#include <memory.h>class Blanks
{
public:Blanks() {}Blanks(int i, double d) {int m = i;double dd = d * 2;}//注意,这里重载了operator new函数,接受两个参数,在使用new运算符时,第一个参数编译器会自动传递进去void *operator new(size_t stAllocateBlock, char chInit);
};
void *Blanks::operator new(size_t stAllocateBlock, char chInit)
{void *pvTemp = malloc(stAllocateBlock);if (pvTemp != 0)memset(pvTemp, chInit, stAllocateBlock);return pvTemp;
}int main()
{//1.理解如何给operator new传递参数//这一句实际上被转换为 Blanks *a1 =  Blanks::operator new(sizeof(Blanks),0xa5);Blanks *a1 = new(0xa5) Blanks;//2.理解何时会屏蔽全局的new	//Blanks *a2 = new Blanks;//error,因为类的自定义new屏蔽了全局new,而自定义的new有两个参数,//3.如何解除类对全局运算符new的屏蔽//即使类中有自定义的new,我们仍然可以通过作用域解析运算符使用全局newBlanks* a3 = ::new Blanks;//4.new运算符如何初始化对象//new的参数是通过new后面括号中参数进行传递的,而Blanks的初始化是通过Blanks其后的括号参数传递进去的Blanks* a4 = new(0xa5) Blanks(1, 7.5);
}

五 使用new可能导致的内存泄漏

如果使用无参数的new操作符,并且编译器设置了 /GX, /EHa, 或者/EHs选项,那么当new抛出异常时,编译器将会产生调用操作符delete的代码。(MSDN原文:If you use the operator new without any extra arguments, and compile with the /GX, /EHa, or /EHs option, the compiler will generate code to call operator delete if the constructor throws an exception. )
但是,如果使用new运算符的placement new形式(即除了分配大小之外还包含其他参数的形式),那么如果构造函数抛出异常,编译器将不支持delete运算符的placements形式。例如:

// expre_new_Operator2.cpp
// C2660 expected
class A {
public:A(int) { throw "Fail!"; }
};
void F(void) {try {//剖出异常时,由于编译器自动产生了::operator delete(void*)代码,指向pal的堆内存会被释放// heap memory pointed to by pa1 will be deallocated// by calling ::operator delete(void*).A* pa1 = new A(10);}catch (...) {	}try {//当A::A(int)抛出异常时,我们应当调用::operator delete(void*, char*, int)//去释放pa2指向的内存;但是因为::operator delete(void*, char*, int) 并没有实现,//析构无法发生,内存出现了泄漏// This will call ::operator new(size_t, char*, int).// When A::A(int) does a throw, we should call// ::operator delete(void*, char*, int) to deallocate// the memory pointed to by pa2.  Since// ::operator delete(void*, char*, int) has not been implemented,// memory will be leaked when the deallocation cannot occur.A* pa2 = new(__FILE__, __LINE__) A(20);}catch (...) {	}
}int main() {A a;
}

http://www.ppmy.cn/news/38907.html

相关文章

miniprogram-to-uniapp使用指南(各种小程序项目转换为uni-app项目)

小程序分类&#xff1a;uni-app qq小程序 支付宝小程序 百度小程序 钉钉小程序 微信小程序 小程序转成uni_app 小程序转为uni_app 小程序转uni_app 小程序转换 工具现在支持npm全局库、HBuilderX插件两种方式使用&#xff0c;任君选择&#xff0c;HBuilderX插件地址&#xff1a…

PDF怎么加密?11 款最好的 PDF 加密软件

保护您的商业文档和机密数据免受黑客攻击和欺诈的愿望可能成为寻找最佳 PDF 加密软件的重要动机。此类程序可防止未经授权的入侵者访问您的数据。 黑客可以访问电子文档和表格&#xff0c;尤其是当您共享它们时。这意味着如果您不保护您的 PDF&#xff0c;您将面临很大的风险。…

Himall商城BillingApplication获取店铺财务总览、根据日期获取该日期的结算周期

/// <summary> /// 获取店铺财务总览 /// </summary> /// <param name"shopId"></param> /// <returns></returns> public static ShopBillingIndex GetShopBillingIndex(long shopId)…

【Java面试八股文宝典之MySQL篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day19

大家好&#xff0c;我是陶然同学&#xff0c;软件工程大三即将实习。认识我的朋友们知道&#xff0c;我是科班出身&#xff0c;学的还行&#xff0c;但是对面试掌握不够&#xff0c;所以我将用这100多天更新Java面试题&#x1f643;&#x1f643;。 不敢苟同&#xff0c;相信大…

含光热电站的冷、热、电综合能源系统优化调度【节点网络】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

C/C++ 变量详解

文章目录前言一、静态变量与动态变量1. 概念2. 区别3. 使用方法和注意事项3.1 静态变量3.2 动态变量4. 结论二、全局变量与局部变量1. 区别2. 全局变量的使用方法和注意事项3. 局部变量的使用方法和注意事项4. 总结前言 对C学习感兴趣的可以看看这篇文章哦&#xff1a;C/C教程…

【从零开始学习 UVM】9.3、UVM Config DB —— uvm_config_db 示例【文章最后的表格对于理解路径索引很重要】

文章目录 Methods规则如何调试 uvm_config_db?示例1. Test and EnvCase #1Case #22. Test, Env, and two Agents推荐实战方式set 与 get 结果由类uvm_config_db访问的UVM配置数据库是在多个TestBench组件之间传递不同对象的好方法。 Methods 有两个主要的函数用于将数据放入…

数据库中,索引详解

数据库索引是一种数据结构&#xff0c;可以提高数据库查询操作的效率。它通过在表中的某一列或多列上创建索引&#xff0c;使得数据库能够更快速地定位到需要查询的数据&#xff0c;从而减少查询的时间和资源消耗。 举一个例子&#xff0c;假设有一个学生信息表&#xff0c;其…