关于new/delete,援引C++ Primer中的一段话:

  某些应用程序对内存分配有特殊的要求,因此我们无法直接将标准的内存管理机制直接应用于这些程序。他们常常需要自定义内存分配的细节,比如使用关键字new将对象放置在特定的内存空间中。为了实现这一目的,应用程序需要重载new运算符和delete预算符以实现内存分配的过程。在讲new/delete系列的重载之前,我们先要明确堆对象构造与析构的过程。

  讲真,对于new/delete的重载,在之前一直不觉得其有什么实质性的作用,直到我加入了一现在的一家公司做数据通信才碰到。

new 或者 new[ ]的作用:

当我们使用一条new表达式时:

string *pstr     = new string("test");
string *pstrArr = new string[];

实际执行了三部操作:

1)new表达式调用一个名为operator new(或者operator new[ ])的标准库函数。该函数的作用是分配一块足够大的内存空间以便存储特定类型的对象。

2)编译器运行相应的构造函数初始化这块内存。

3)对象被分配空间完成,返回一个带类型的指针指向这块个象。

delete delete[ ]的作用

delete pstr;
delete [] pstrArr;

1)调用指针所指对象执行析构函数。

2)编译器调用operator delete(或者operator delete[ ])的标准函数释放内存。

new/delete默认重载

C++默认的几种重载模型如下

//标准版本(常用)
void *operator new(size_t,nothrow &);
void *operator new[](size_t,nothrow &);
void *operator delete(void *,nothrow &) noexcept;
void *operator delete(void *,nothrow &) noexcept;

//不会抛出异常的版本
void *operator new(size_t,nothrow &) noexcept;
void *operator new[](size_t,nothrow &) noexcept;
void *operator delete(void *,nothrow &) noexcept;
void *operator delete(void *,nothrow &) noexcept;
#include <iostream>
#include <new> using namespace std; int main(int argc, char *argv[])
{
double * ptr[];
for(int i=; i<; i++)
{
ptr[i] = new double[];
if(ptr[i] == NULL)
{
cout<<"new error"<<endl;
exit();
} cout<<"ptr["<<i<<"]"<<"->50000000 duble"<<endl;
}
return ;
}

运行结果:

#include <iostream>
#include <new> using namespace std; int main(int argc, char *argv[])
{
double * ptr[];
for(int i=; i<; i++)
{
ptr[i] = new (nothrow)double[];
if(ptr[i] == NULL)
{
cout<<"new error"<<endl;
exit();
} cout<<"ptr["<<i<<"]"<<"->50000000 duble"<<endl;
}
return ;
}

运行结果:

模仿默认实现重载

以上函数既可以全局重载,也可以成员重载,全局重载不常用,下面是成员重载的方式,全局的重载只需要将他们拷贝到类外就好了。

#include <iostream>

using namespace std;

class A
{
public:
A(){
cout<<"A constructor"<<endl;
}
~A(){
cout<<"A destructor"<<endl;
} void * operator new (size_t size)
{
cout<<"new "<<size<<endl;
return malloc(size);
} void operator delete(void *p)
{
cout<<"delete"<<endl;
free(p);
} void * operator new[] (size_t size) noexcept
{
cout<<"new[] "<<size<<endl;
return malloc(size);
} void operator delete[](void *p) noexcept
{
cout<<"delete[] "<<endl;
free(p);
} void dis()
{
cout<<"dis"<<endl;
} private:
int a;
double d;
}; using Func = void (*)(); int main(int argc, char *argv[])
{
cout<<"sizeof(A):"<<sizeof(A)<<endl; cout<<"\n---new A---"<<endl;
A * newA = new A; cout<<"---new A[1]---"<<endl;
A * newA1 = new A[]; cout<<"\n---new A[2]---"<<endl;
A * newA2 = new A[]; cout<<"\n---delete A---"<<endl;
delete newA; cout<<"\n---delete A[1]---"<<endl;
delete newA1; cout<<"\n---delete A[2]--"<<endl;
delete [] newA2; return ;
}

运行结果:

new[ ]分配内存大小与期望不一致的问题

调用new[]函数的时候,其实际生成的内存大小与对象个数并不成比例,

比如A *p = new A[10];

实际上生成的内存大小并非sizeof(A) * 10,还要生成一个内存大小计数

这个要归咎于自定义对象是否自定义了析构函数,如果没有自定义析构函数,那么编译器会优化内存申请,不会多出四个字节。

详情请查看这篇博客:

https://www.cnblogs.com/tp-16b/p/8684298.html

关于C++内存操作符重载的特别声明

1)   隐式静态,成员重载不可在函数操作数据成员变量(加 static修饰也不会有问题)。

2)operator new/operator new[ ]第一个参数必须是size_t类型,且不能含有默认实参。

3)operator new/operator new[ ]重载时,可以提供额外的参数,使用时必须使用new的定位形式将实参传给定位的形参。

4)void * new(size_t , void *)这个函数只有系统提供的版本,不可重载。

5)operator delete/operator delete[ ]第一个形参必须是void *,该函数还可以包含一个size_t的形参。

new[1] 与new 的不同

operator new /new[ ]  /// operator delete /delete[ ]与其他运算符重载的异同

operator new 和 operator delete实际上是和其他的operator函数(如operator =)是不一样的,这两个函数并没有重载new 或者 delete表达式,实际上我们根本无法自定义表new或者delete达式的行为。

我们提供的operator new 函数和 operator delete函数覅热目的在于改变内存的分配方式,当不管怎么样不能改变new运算符和delete运算符的基本含义。

占位符问题:

既然说到了new/delete重载,那么我们就说一下占位符的问题。

空的结构体/空类默认有一个占位符。所以默认大小是一个字节。

例如:

#include <iostream>

using namespace std;

class EmptyClass
{}; struct EmptyStruct
{}; class EmptyClassWiFlag
{
char ch[];
}; struct EmptyStructWithFlg
{
char ch[];
}; int main(int argc, char *argv[])
{
cout<<"sizeofClass:"<<sizeof(EmptyClass)<<endl;
cout<<"sizeofClass:"<<sizeof(EmptyStruct)<<endl;
cout<<"sizeofClass:"<<sizeof(EmptyClassWiFlag)<<endl;
cout<<"sizeofClass:"<<sizeof(EmptyStructWithFlg)<<endl;
return ;
}

那么当我们在一个空类中声明一个类型的数组,并且数组元素个数设置为0时他就真的成了一个空类。

这种真正的空类有什么用呢?

如上我们有时会重载operator new / operator new [ ] ,有时我们会根据项目需求申请超出类大小的内存。那么如何拿到这个类对象还没用到的内存就是一个问题。char [0]这时就派上用场了。

这里重载一个new的定位形式placer new,来展示空占位符的作用:

#include <iostream>
#include <string.h> using namespace std; class EmptyClassWiFlag
{
public:
EmptyClassWiFlag()
:a()
{}
~EmptyClassWiFlag()
{}
void* freeMemBegain()
{
return ch;
}
private:
int a;
char ch[];
}; void* operator new(size_t memSize,size_t reqMemSize)
{
cout<<"memsize:"<<memSize<<endl;
cout<<"reqMemSize:"<<reqMemSize<<endl;
return malloc(reqMemSize);
} #define MEMCOUNT 100
int main(int argc, char *argv[])
{
EmptyClassWiFlag * p1 = new (MEMCOUNT)EmptyClassWiFlag;
cout<<"addrOfP1:"<<p1<<endl;
cout<<"freeMemBegainAddr:"<<p1->freeMemBegain()<<endl;
memset(p1->freeMemBegain(),0x00,MEMCOUNT-sizeof(EmptyClassWiFlag));
char *flag = "hello world";
memcpy(p1->freeMemBegain(),flag,strlen(flag)+);
cout<<(char*)p1->freeMemBegain()<<endl;
return ;
}

成员变量char ch[0]起到了对象占用内存截至,剩余内存开头的作用。

运算符重载之new与delete的更多相关文章

  1. C++运算符重载 模板友元 new delete ++ = +=

    今天的重载是基于C++ 类模板的,如果需要非类模板的重载的朋友可以把类模板拿掉,同样可以参考,谢谢. 一.类模板中的友元重载 本人喜好类声明与类成员实现分开写的代码风格,如若您喜欢将类成员函数的实现写 ...

  2. 深入C++的运算符重载

    对于简单的运算符,可以参考之前的博文.之后会有一篇关于从等号运算符重载的角度研究深浅拷贝的博文.这里是讲:逗号,取成员运算符,输入输出运算符,下标运算符,括号,new和delete的重载. 逗号运算符 ...

  3. 类型转换运算符、*运算符重载、->运算符重载、operator new 和 operator delete

    一.类型转换运算符 必须是成员函数,不能是友元函数 没有参数 不能指定返回类型 函数原型:operator 类型名();  C++ Code  1 2 3 4 5 6 7 8 9 10 11 12 1 ...

  4. C++运算符重载

    C++运算符重载 基本知识 重载的运算符是具有特殊名字的函数,他们的名字由关键字operator和其后要定义的运算符号共同组成. 运算符可以重载为成员函数和非成员函数.当一个重载的运算符是成员函数时, ...

  5. 标准C++之运算符重载和虚表指针

    1 -> *运算符重载 //autoptr.cpp     #include<iostream> #include<string> using namespace std ...

  6. c/c++面试题(6)运算符重载详解

    1.操作符函数: 在特定条件下,编译器有能力把一个由操作数和操作符共同组成的表达式,解释为对 一个全局或成员函数的调用,该全局或成员函数被称为操作符函数.该全局或成员函数 被称为操作符函数.通过定义操 ...

  7. 实验12:Problem H: 整型数组运算符重载

    Home Web Board ProblemSet Standing Status Statistics   Problem H: 整型数组运算符重载 Problem H: 整型数组运算符重载 Tim ...

  8. C++学习26 运算符重载的概念和语法

    所谓重载,就是赋予新的含义.函数重载(Function Overloading)可以让一个函数名有多种功能,在不同情况下进行不同的操作.运算符重载(Operator Overloading)也是一个道 ...

  9. C++运算符重载——重载特殊运算符

    1.重载赋值运算符= 赋值运算符用于同类对象间的相互赋值.赋值运算符只能被重载为类的非静态成员函数,不能重载为友元函数和普通函数. 对于用户自定义的类而言,如果没有重载赋值运算符,那么C++编译器会为 ...

随机推荐

  1. 希尔排序——C语言

    希尔排序 希尔排序是插入排序的一种,又称“缩小增量排序”,希尔排序是直接插入排序算法的一种更高效的改进版本,关于插入排序可以看下这篇随笔:插入排序——C语言 (图片来源:https://www.cnb ...

  2. 【判环】Perpetuum Mobile

    Perpetuum Mobile 题目描述 The year is 1902. Albert Einstein is working in the patent office in Bern. Many ...

  3. SAS学习笔记16 SAS创建计数(枚举)变量

  4. SSM集成

    SSM集成   Spring和各个框架的整合   Spring目前是JavaWeb开发中最终的框架,提供一站式服务,可以其他各个框架整合集成   Spring整合方案   SSH Ssh是早期的一种整 ...

  5. Istio技术与实践03:最佳实践之sidecar自动注入

    Istio通过对serviceMesh中的每个pod注入sidecar,来实现无侵入式的服务治理能力.其中,sidecar的注入是其能力实现的重要一环(本文主要介绍在kubernetes集群中的注入方 ...

  6. 关于UBOOT,LINUX内核编译,根文件系统的15个小问题

    (1)内核默认运行地址和加载地址在哪里设置? 由 arch/arm/kernel/vmlinux.lds.S 生成的 arch/armkernel/vmlinux.lds决定   (2)从FLASH什 ...

  7. Python练习_考试第二次

    一. 选择题(32分) 1. python不支持的数据类型有:AA. charB. intC. floatD. list 2. Ex = ‘foo’y = 2print(x + y)A. fooB. ...

  8. 关于el-select 单选与多选切换的时候报错的解决办法

    错误: 出错原因: 估计是单选切换到多选的时候元素没有刷新的原因,猜测 解决办法: 1.在el-select上面加上一个条件判断, 条件判断中绑定一个变量值 例如 :multiple="is ...

  9. centos7安装harbor

    harbor是什么? docker容器是集装箱,harbor就是放集装箱的港湾. 一.下载软件: github上选择对应版本下载 https://github.com/goharbor/harbor/ ...

  10. 阿里P7分享如何面对枯燥的源码

    一个软件开发人员,工作到了一定的年限(一般是3.4年左右),如果他还没学会阅读源码,那么他就会遇到瓶颈.因为到了这个时候的开发,他应该不仅仅只会做那些 CURD 的业务逻辑,而应该会根据公司的实际情况 ...