new、express new、operator new、placement new 之间的千丝万缕

银河麒麟v10-arm版离线安装Portainer 阅读详情


一、new is an expression

在C++中,new经常被用来进行动态内存的分配,例如:

Foo* obj = new Foo; //其中Foo是一个类

这种我们经常使用的new其实在C++中称为“expression”。一个expression是没办法重载的。所以,我们没办法从 expression new入手来定义一个我们想要的版本。但是,expression new 经过编译器之后执行的动作却让我们有机可趁。在一个new expression 之后,编译器其实做了三件事:

1. void* men = operator new(sizeof(Foo));
2. Foo* ptr = static_cast<Foo*>(men);
3. ptr->Foo("waterMelon", 30)
  1. 第一,编译器帮我们调用了operator new 来 申请一块干净的内存(未经过初始化)。
  2. 第二,编译器帮我们把得到那块内存用static_cast函数进行类型转换成对应的类型。(C++的四种cast动作详解)
  3. 第三,编译器帮我们调用了构造函数,在分配的内存中进行对象的构造。

在这个过程中,让我们有机可趁的地方是步骤一中的 operator new 函数,因为它是一个可以被进行重载的函数。 在步骤一中 operator new 接收了一个参数,指明了“我想要多少字节的内存”。而对于后续的步骤二、三来说,从步骤一中得到的内存究竟是从哪儿来的其实它们并不关心,毕竟它们的职责只是构造对象而已。因此,我们就能对某些版本的operator new进行重载,来进行内存管理或者做其他的事情。


二、operator new

为了验证上面的事情(使用expression之后,编译器做了三件事),我们可以使用下面的程序进行测试:

#include <iostream>
#include <cstdlib>
#include <string>

using namespace std;

class Foo {
public:
	Foo() = default;
	Foo(string name, int value) : _name(name), _value(value) {}
	void showContent() 
	{ cout << "name:" << _name << " " << "value:" << _value << endl; }
	
	void* operator new(size_t size); //重载operator new 操作

private:
	string _name;
	int _value;
};

void* Foo::operator new(size_t size) {
	cout << "call my operator new" << endl;
	return malloc(size);
}

int main() {
	Foo* obj = new Foo("waterMelon", 30);
	obj->showContent();

	delete obj;

	return 0;
}

在类Foo 中,我们定义了自己的operator new 版本。因此,在主程序中,一旦使用了expression new的操作,编译器就自动帮我们做三件事,而第一件事就是调用类作用域内重载的operator new,因此,在控制台会对结果进行输出:

在这里插入图片描述
可以看见,确实new expression 确实调用了我们自己重载的new operator版本。然而,对于后续的转型、构造来说,它们并不关心这块内存究竟是哪儿来的。在上面的例子中,这块内存其实只是简单的调用C中的malloc,而在我们使用的STL容器中,这块内存其实是从内存池(由allocator类维护)挖出来的,每次需要的时候,就从自己维护的内存池中分配一块出来返回,进行后续的转型和初始化。这是STL分配器中进行内存管理的方法,为的是减少不必要的空间浪费(cookie)和减少malloc调用。当然,这逐渐偏离了现在写的主题。

总的来说,operator new 其实就是给了我们一个机会来进行函数的重载,让我们有办法去“什么地方”拿一块内存而已。

三、placement new(一)

上面讲到了使用expression new之后发生的三件事以及我们如何使用operator new 来抓住“内存分配”这个动作进行重载,得到我们想要的版本。对于步骤二、步骤三是编译器帮我们做的事情。现在的问题是,编译器做的事情,你自己能做吗?
假设我从哪儿拿到了一块干净的内存,我能不能自己转型、然后调用构造函数来构造对象?以下为测试程序:

#include <iostream>
#include <cstdlib>
#include <string>

using namespace std;


class Foo {
public:
	Foo() = default;
	Foo(string name, int value) : _name(name), _value(value) {}
	void showContent() 
	{ cout << "name:" << _name << " " << "value:" << _value << endl; }
	
	void* operator new(size_t size); //重载operator new 操作

private:
	string _name;
	int _value;
};

void* Foo::operator new(size_t size) {
	cout << "call my operator new" << endl;
	return malloc(size);
}

int main() {
	Foo* obj = new Foo("waterMelon", 30);
	obj->showContent();
	delete obj;
	
	// 尝试在干净的内存中调用构造函数
	void* men = malloc(sizeof(Foo));
	Foo* ptr = static_cast<Foo*>(men);
	ptr->Foo("waterMelon", 30);
	
	return 0;
}

结果如下:
在这里插入图片描述
看来我们有点异想天开了,编译器根本不允许我们自己来“转型+构造”。那有没有可能C++也提供了什么函数来完成这两个步骤?答案已经呼之欲出了,那就是使用placement new。placement new 允许我们在一块已经分配的内存中来进行构造函数的调用。注意,下面是placement new 的用法:

// 注意,下面 place_address 是某块用来构造对象的内存(指针),type是某个类的名称
new (place_address) type  // 默认构造
new (place_address) type (initializers) // 传入参数,见下面的例子

实用:

#include <iostream>
#include <cstdlib>
#include <string>
#include <new>

using namespace std;


class Foo {
public:
	Foo() = default;
	Foo(string name, int value) : _name(name), _value(value) {}
	void showContent() 
	{ cout << "name:" << _name << " " << "value:" << _value << endl; }
	
	void* operator new(size_t size); //重载operator new 操作

private:
	string _name;
	int _value;
};

void* Foo::operator new(size_t size) {
	cout << "call my operator new" << endl;
	return malloc(size);
}

int main() {

	void* men = malloc(sizeof(Foo));
	Foo* obj2 = ::new(men) Foo("banana", 20); // 使用placement new进行对象的构造
	obj2->showContent();
	delete obj2;

	return 0;
}

执行结果:
在这里插入图片描述
从上面可以总结得到,placement new就是标准库开放给我们在一块已经分配的内存中来进行构造函数的调用的接口而已。再次注意:由于placement new动作是“构造”而不是“分配”,因此,没有对应的placement delete。而上面的new、operator new 都涉及了内存的分配,因此有对应的delete、operator delete(可重载)。


四、placement new(二)

在placement new(一)中我们从new、express new、operator new、placement new出发讲述了关于placement new在整个故事中解决的问题和用法。在实际应用中,placement new还有一种用法:placement new 是可以用来分配内存的。相比于直接使用 expression new, placement new可以接受额外的参数。比如,在分配内存的过程中,计算机没有足够的内存可以分配了,那么相比于expression new, placement new可以接受额外的的参数 nothrow来禁止在没有足够内存的情况下抛出异常:

int* p1 = new int; //分配失败则抛出异常
int* p2 = new(nothrow) int; //分配失败只会返回一个空指针

在实现上,placement new 其实调用的是operator new。实际上,placement new可以很多形式,括号内的参数也可以有很多种。不同的形式其实只是对应了不同的operator new的重载版本而已。

比如上面的例子中,

new(nothrow) 
调用
operator new(size_t size, nothrow_t&) noexcept;

而在placement new(一)中,

::new(men) Foo("banana", 20)
调用
operator new(size_t size, void*);

注意参数 size_t size 是编译器来传值的,所以在调用的时候只需要传一个参数就好。那这里为什么要使用全局的placement new来调用呢?原因就是这个版本的operator new是不允许用户进行重载的:

operator new(size_t size, void* address) {
	return address;
}

总结:

至此,所有的故事已经讲完,关于它们之间的关系,以下图一图概之:

![在这里插入图片描述](https://img-blog.csdnimg.cn/45fd3cae611d49cf82b5afdebc7c4912.png

COMSOL新手必看:锂电池仿真从入门到精通的5个实战案例(附模型文件) 本文为COMSOL新手提供锂电池仿真的5个实战案例,涵盖锂枝晶生长模拟、电池热失控预警、SOC估计等效电路模型、电池组液冷系统热管理及SOH衰退模型构建。通过详细步骤和参数设置,帮助用户快速掌握锂电池仿真技术,提升新能源研发效率。附模型文件可直接应用。 阅读详情

相关推荐

新手必看:5分钟搞定Unitree Go1机器狗Gazebo仿真运动控制(附按键图解)

本文为新手提供了Unitree Go1机器狗在Gazebo仿真环境中实现运动控制的快速指南。通过5分钟极简配置和按键图解,帮助开发者快速掌握宇树科技明星产品的基础操作,包括环境搭建、仿真启动和运动控制技巧,适合零基础用户快速上手。

weixin_42550597的博客 300

Expression 表达式目录树

Expression 表达式目录树

againknow的博客 585

小样本学习概述

目录 前言 小样本学习存在的意义? 什么是小样本学习? 小样本学习的方法有哪些? 结语 前言 小样本学习(Few-Shot Learning)是近几年兴起的一个研究领域,小样本学习旨在解决在数据有限的机器学习任务。 小样本学习存在的意义? 近些年,以深度卷积神经网络为代表的深度学习方法在各类机器学习任务上取得了优异的成绩——很多任务上已经超越了人类表现。狂欢背后,危机四伏。因为这些深度学习方法work的关键之一是海量标注数据的支持。 但是在工业界,很多时候难以获得海量的训练数据,更.

DwD-的博客 3万+

Mybatis Plus 数据权限插件坑点

原创 rookie发表于广东。

竹林幽深 848

Codesys电子凸轮功能块MC_CamIn的masteroffset和slaveoffset参数的使用

问题1:在Codesys使用作轴电子凸轮时,MC_CamTableSelect的参数设置为MasterAbsolute=1,SlaveAbsolute=1,此设置为绝得模式, 此时主从轴当前位置都不为零时,此时Masteroffset=0、slaveOffset=0,凸轮启时出现突变,实轴无法启动。问题分析:在觉得模式下,主轴位置和从轴为是对应凸轮表的相位的,Masteroffset=0、slaveOffset=0,启动点时在主轴位置=0,从轴位置=0的点启动,此时主轴当前位置不为零则主从轴位置发生突变,

weixin_43587371的博客 1801

使用freesurfer和3Dslicer进行脑区分割和电极定点(详细版)

1、安装好matlab和freesurfer2、在目标位置创建文件夹3、开放权限4、将需要计算的nii文件放入文件夹。

zQIANYUN的博客 5622

MybatisPlus 实现数据拦截

基于配置文件实现(关键key存储在配置文件,通过读取配置文件来实现动态拼接sql)3、创建拦截器重写InnerInterceptor接口,重写查询方法。2、配置枚举类配置文件 EDataPermissionType。4、创建处理类,动态拼接sql片段,设置where。5、将拦截器加到mybatis-plus插件中。注:id用以区分是小程序还是应用程序。6、使用的位置加注解即可生效。app:应用程序类型权限控制。mp:小程序类型权限控制。

just love code 7285

解决vLLM安装卡在vllm-nccl-cu12依赖项的实战指南

本文提供了解决vLLM安装过程中卡在vllm-nccl-cu12依赖项的详细指南。通过分析CUDA版本兼容性问题,介绍了三种实用解决方案:安装指定版本vLLM、手动安装nccl-cu12依赖以及升级CUDA到12.x版本,并特别针对离线环境提供了安装建议,帮助开发者顺利完成vLLM的安装部署。

weixin_29098367的博客 514

mybatis-plus sql改写插件(二)

去年基于租户过滤,权限拦截都是where tenant_id = XXX, 今年遇到一个物联网行业的需求,需要实现的是where sn in {sns} , 可能存在子查询,例如: where xinfo_id in (select x_id from x_info where sn in {sns}) ,这里的变量存在5个:xinfo_id,x_id,x_info,sn,sns 会有如下参数对象定义。

大蚊子 518

KEPServer EX6的Modbus、MQTT和REST Server一站式配置整理说明

KEPServer EX6的Modbus、MQTT和REST Server一站式配置整理说明

syqenter的专栏 8961

【QGIS】软件下载安装及GIS4WRF插件使用

【QGIS】软件下载安装及GIS4WRF插件使用

WW、forever的博客 1799

第十五节:Expression表达式目录树(与委托的区别、自行拼接、总结几类实例间的拷贝)...

一. 基本介绍 回忆: 最早接触到表达式目录树(Expression)可能要追溯到几年前使用EF早期的时候,发现where方法里的参数是Expression<Func<T,bool>>这么一个类型,当初不是很理解,只是知道传入lambda表达式使用即可,对于Expression和里面的Func<T,bool>到底是怎么一种关系,都不清楚。   今天,带着...

weixin_34256074的博客 290

使用R语言实战:构建Logistic回归模型

Logistic回归是一种基于概率的分类算法,它用于预测离散的结果变量(也称为响应变量或因变量)。与线性回归不同,Logistic回归的输出是一个概率值,通常在0和1之间。这使得它非常适合处理二分类问题。市场营销:预测客户是否会购买某个产品。医学研究:预测疾病的发生与否。信用风险评估:预测个人申请贷款的违约风险。社会科学:预测个人对某个政策的态度。

2301_79331230的博客 1139

从零学习表达式树(七)【值类型、引用类型、泛型、集合、调用函数】

C# 表达式树中,定义一个变量,使用。创建变量结点的方法有两种,Expression . Parameter() Expression . Variable() // 另外,定义一个常量可以使用 Expression.Constant()。两种方式都是生成类型和Variable()都具有两个重载。他们创建一个节点,该节点可用于标识表达式树中的参数或变量。用于在块内声明局部变量。用于声明输入值的参数。先看第一种}从代码来看,没有区别。再看看具有两个参数的重载}}

极客神殿 728

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

C#的λ表达式树(LambdaExpression)保姆级超详细简单入门教程

有看过我之前发表过的C#相关文章分享和阅读过我代码的朋友们可能会在我的代码里面经常看到各种各样的λ表达式动态拼接,C#的λ表达式树是一个好东西,也是别的语言学不来的,熟悉掌握λ表达式就能够实现各种linq场景的个性化操作,如动态拼接查询条件、排序方式等,也能够实现替代反射的高性能操作,比如我们常用到的IQueryable和IEnumerable,每个扩展方法就全是λ表达式树。 之前也分享过几篇关于λ表达式的文章,但鉴于还是有一部分初学者对于λ表达式树不太懂的,而网络上关于λ表达式树的科普不是很全面,今天就

wori的专栏 2077

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

MyBatis Plus 拦截器实现数据权限控制

上篇文章介绍的MyBatis Plus 插件实际上就是用拦截器实现的,MyBatis Plus拦截器对MyBatis的拦截器进行了包装处理,操作起来更加方便MyBatis Plus提供的InnerInterceptor接口提供了如下方法,主要包括:在查询之前执行,在更新之前执行,在SQL准备之前执行/**return;} } /*** 查询。

progammer10086的博客 5732

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

在EF中使用Expression自动生成p=>new Entity(){X="",Y="",..}格式的Lambda表达式灵活实现按需更新...

一、基本介绍 ...

weixin_30762087的博客 360

C# .Net学习笔记—— Expression 表达式目录树

C#学习,表达式目录树

weixin_46711336的博客 2323

.NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)

https://blog.youkuaiyun.com/WPwalter/article/details/83744781 大家都说反射耗性能,但是到底有多耗性能,哪些反射方法更耗性能;这些问题却没有统一的描述。 本文将用数据说明反射各个方法和替代方法的性能差异,并提供一些反射代码的编写建议。为了解决反射的性能问题,你可以遵循本文采用的各种方案。 本文内容 反射各方法的性能数据 反射的高性能开发建议 创...

坚定的走 6168

Orm框架开发之NewExpression合并问题

之前都是看别人写博客,自己没有写博客的习惯.在工作的过程中,总是会碰到许多的技术问题.有很多时候想记录下来,后面一直有许多的问题等着解决.总想着等系统完成了,再回头总结下.往往结果就把这事抛到脑后了. 总觉得不能一直这样哈.今天简单记一下吧.有表达不清楚的地方,多多包涵. 最近在研究.net orm框架.想开发一套更好用的Orm框架.别嫌轮子多.碰到一个Expression合并的问题. 一....

weixin_30929195的博客 296

C#使用Expression.Call动态调用方法

怎么动态调用方法呢?可以用委托啊,如果方法名称是文本或者写在配置文件里呢?可以用反射来调用,这需要写一堆代码,而且反射效率不是很高。这时就可以使用C#的Expression.Call方法来动态调用,这不是表达式树吗?是的,它也可以动态调用,它的本质上还是委托。Expression.Call是.NET Framework4.0之后出现的特性,可以用来动态调用方法。下面举例说明它的使用。首先试...

zls365365的博客 1494

Expression 构建表达式树做类型的转换

表达式树常用在需要动态生成表达式的地方,比如将QueryPrameter对象动态的生成查询表达式,通过EF获取查询结果。 对表达式树的编写就是一个拼装过程,这点需要注意。 以下方法Map,动态的生成new一个对象的表达式。 Demo中使用该方法生成“Person对象转为DTO对象”的表达式。 namespace ConsoleDemo { class Program { static void Main(string[] args) {

m0_62367247的博客 587

java new expression_yii2 new Expression

yii2 new Expression,new Expression,new \yii\db\Expression,yii2FIND_IN_SET,yii sql,yii\db\Expression演示一段yii2 关于 new Expression 或者 new \yii\db\Expression 的sql语句首先use yii\db\Expression;$params = ['1','2...

weixin_42502419的博客 216

yii2 new Expression

yii2 new Expression,new Expression,new \yii\db\Expression,yii2FIND_IN_SET,yii sql,yii\db\Expression 演示一段yii2 关于 new Expression 或者 new \yii\db\Exp...

592

C++——禁止对象产生在heap

有些类的对象我们并不希望在堆区开辟,就需要禁止对象在heap开辟的这样的手法。

JAN6055的博客 728

placement new 时出现错误:error C2660: “operator new”: 函数不接受 2 个参数

包含头文件new.h即可 #include <new.h>

gergul的专栏 2428
上一篇: Linux下Makefile操作
下一篇: C++ 手写智能指针 shared_ptr
zsiming
博客等级 码龄7年 43粉丝 74原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值