关于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. Win10 收件箱添加QQ邮箱(2019年5月19日)

    Emmm弄的时候没截图,就语言描述吧,非常简单. 登录到网页端QQ邮箱.点我登录 登录之后,界面上端的Logo右边有个"设置"(字有点小).点它 邮箱设置下面有一堆标签,点击&qu ...

  2. diy操作系统 附录:gcc栈帧开启与关闭

    在gcc命令行参数中可以使用-fno-omit-frame-pointer来开启栈帧的使用,或者使用-fomit-frame-pointer选项来关闭. 然而,也可以针对某一个函数进行配置方法如下,这 ...

  3. 关于scanf()读取与返回值和回车键的问题

    今天老师检查的时候说如果一个链表为空(简单的说就是while(scanf())一开始没输入数字就按回车的话会进入死循环)的情况, 我当时有点懵,因为文档里强调为空的情况.还好老师叫我自己现场实现一下, ...

  4. 一个农民工自学java找到工作的励志故事

    <!-----------------------------------------------------------------------------摘自网络-------------- ...

  5. access 数据库创建表SQL语法

    create table R_CAIFA_B13 ( ID AUTOINCREMENT PRIMARY KEY, XB varchar(255), C1 varchar(50), C2 varchar ...

  6. ASP.NET MVC或者.net Core mvc 页面使用富文本控件的 保存问题

    https://blog.csdn.net/leftfist/article/details/69629394 目前在做的项目存在XSS安全漏洞! 原因是有一些页面使用了富文本编辑框,为了使得其内容可 ...

  7. java 框架-缓冲-Redis 2Jedis操作

    https://www.cnblogs.com/wlandwl/p/redis.html Redis介绍及Jedis基础操作   1.Redis简介 Redis 是一个开源(BSD许可)的,内存中的数 ...

  8. 一步一步教你实现iOS音频频谱动画(一)

    如果你想先看看最终效果再决定看不看文章 -> bilibili 示例代码下载 第二篇:一步一步教你实现iOS音频频谱动画(二) 基于篇幅考虑,本次教程分为两篇文章,本篇文章主要讲述音频播放和频谱 ...

  9. TDDL生成全局ID原理

    TDDL 在分布式下的SEQUENCE原理 TDDL大家应该很熟悉了,淘宝分布式数据层.很好的为我们实现了分库分表.Master/Salve.动态数据源配置等功能. 那么分布式之后,数据库自增序列肯定 ...

  10. 【Redis】分布式Session

    一.问题引出 1.1 Session的原理 1.2 问题概述 二.解决方案 三.代码实现-使用Token代替Session 3.1 Service 3.2 TokenController 一.问题引出 ...