new和delete不同用法


基本用法

int * aptr = new int(10);

delete aptr, aptr = nullptr;

上面的代码是我们最基本也是最常见的使用new和delete的方式,当编译器运行int * aptr = new int(10); 这行代码时,其实是分为两个步骤来执行,第一步,调用operator new(size_t size) 分配内存;第二步在分配的内存上调用placement new(void * ptr) T(); “定位放置 new”,就是把对象构建在指定的ptr指向的内存上,换句话就是在指定的内存上调用构造函数。

概念区分

new operator 和 delete operator :new 和 delete 操作符(关键字),无法重载

operator new 和 operator delete:两个函数用来服务于 new 和 delete 操作符,以及对应的 operator new [] , operator delete [] 对应于 new [] 和 delete []相关的数组操作;这两个函数是可以被重载的,一般有全局默认的函数,自己也可以定义自己的,在定义C++类的时候也可以为某个class定制对应的 operator new 和 operator delete

全局的operator new 和 operator delete函数

全局默认operator new 函数:

void * operator new(std::size_t count) throw(std::bad_alloc); //normal new
void * operator new(std::size_t count, void *ptr)throw(); //placement new
void * operator new(std::size_t count, const std::nothrow_t&)throw();//nothrow new, 为了兼容以前的版本

我们可以根据自己的意愿来重载 不同版本的operator new 函数来覆盖掉全局的函数,对于申明的类,可以在类中申明对应的static void * operator new(size_t size); 来为该类定制自己的operator

operator new的不同重载办法

#include <iostream>
#include <new>
#include <cstring>
#include <cstdlib>
using namespace std;
/*
首先自己定义operator new函数,来替代编译器全局默认的operator函数
*/
void * operator new(size_t size){
cout<<"global Override operator new"<<endl;
void * ptr = malloc(size); //自己调用malloc来分配内存
return ptr;
//下面这句话会引起递归的调用,重载operator new之后,::operator new就等于调用自己
//return ::operator new(size);
}
//重载版本的operator new,该函数默认的是调用 上面的operator new函数
void * operator new(size_t size, int flag){
cout<<"global Override operator new: "<<flag<<endl;
return (::operator new(size));
}
//覆盖掉全局的operator delete 函数
void operator delete (void * ptr){
cout<<"global Override operator delete"<<endl;
free(ptr);
ptr = nullptr;
}
/*
重载版本的operator delete,该函数主要的用途是在构造函数执行不成功的时候,调用与new 函数对应的 delete来释放,稍后会有对应的列子来介绍,在这个例子中该函数暂时没用
*/
void operator delete (void * ptr, int flag){
cout<<"Override operator delete: "<<flag<<endl;
::operator delete(ptr);
ptr = nullptr;
}
int main(){
int * ptr = new int(10);
/*
delete ptr; 调用的就是 void operator delete(void * ptr); 而与new 匹配的delete 不是自己调用的,而是在new申请,成功却在构造函数时候出错,new operator自己根据operator new 来寻找 对应的operator delete 来调用,稍后介绍。
*/
delete ptr;
cout<<endl<<"*********************"<<endl<<endl;
ptr = new(20) int(10);
delete ptr;
return 0;
}

上面的程序的输入如下面:

从上面的结果可以看出,new int(10);直接先调用 operator new(size_t size); 由于int没有构造函数,在那块内存上调用int的构造函数; 在delete ptr; 的时间直接调用 operator delete(void * ptr);这个函数

new(20) int(10);的时候,则调用重载版本的 operator new(size_t size, int flag); 而该函数有调用了 operator new(size_t size); 函数,释放的时候delete ptr;还是直接只调用operator delete(void * ptr);(注:这里初步提出为啥不调用operator delete(void * ptr, int flag); 这个函数来释放ptr ???因为它的用途不在这,而在于下面将要讲的。

针对类定制版本的operator new 和 operator delete

#include <iostream>
#include <new>
#include <cstring>
#include <cstdlib>
using namespace std;
//下面的operator new 和 operator delete 和上面的代码一样,替代默认全局函数
void * operator new(size_t size){
cout<<"global Override operator new"<<endl;
void * ptr = mallo![](http://images2015.cnblogs.com/blog/1042615/201610/1042615-20161021150145638-80575599.jpg)
c(size);
return ptr;
}
void * operator new(size_t size, int flag){
cout<<"global Override operator new: "<<flag<<endl;
return (::operator new(size));
}
void operator delete (void * ptr){
cout<<"global Override operator delete"<<endl;
free(ptr);
ptr = nullptr;
}
//这次主要体现该函数的用法*********************
void operator delete (void * ptr, int flag){
cout<<"Override operator delete: "<<flag<<endl;
::operator delete(ptr);
ptr = nullptr;
}
class Base{
public: Base(){
cout<<"Base construct"<<endl;
throw 2;
}
/*
类中定制的operator new会覆盖全局的函数,但可以通过简单的调用全局的函数来实现调用
*/
static void * operator new(size_t size){
cout<<"operator new of Base"<<endl;
return ::operator new(size); //调用全局的operator new
}
static void * operator new(size_t size, int flag){
cout<<"Override operator new of Base: "<<flag<<endl;
return operator new(size);
}
static void operator delete(void * ptr){
cout<<"Operator delete of Base"<<endl;
::operator delete(ptr);
}
static void operator delete(void * ptr, int flag){
cout<<"Override operator delete of Base: "<<flag<<endl;
operator delete(ptr);
}
int x;
int y ;
};
int main(){
try{
Base * bptr = new(20) Base;
}
catch(...){
cout<<"catch a exception"<<endl;
}
return 0;
}

上面的函数,在Base的构造函数中,抛出一个异常(忽略什么异常,主要用来模拟),直接上运行结果图:

如上图所示的运行结果,new(20) Base首先调用类中定制的 operator new(size_t size, int flag); 然后在调用 operator new(size_t size); 在调用全局的 operator new(size_t size);申请完内存之后,在调用类的构造函数,此时会抛出异常,这个时候,由于调用的 operator new(size_t size, int flag);函数来申请内存,但构造函数失败了,此时 new - operator (new 关键字,区分operator new)会调用和 operator new 相同参数的operator delete函数来释放已经申请的内存,因此operator delete(void *ptr, int flag) ,在调用operator delete(void * ptr); 在调用全局的operator delete(void * ptr);

若是不给Base类重载 static void operator delete(void * ptr, int flag);这个函数,结果则如下图:

这个例子就说明,不定制对应的operator delete(), 则绝不会调用默认的operator delete函数来释放内存,这里就会导致内存泄露。因此在为某个class 定制 operator new函数的时候,如果重载了不同参数的operator new,应该定制对应版本的operator delete(); 这里对应版本是参数是对应的;(这里也就是 operator delete(void * ptr, int flag);的主要用途,当构造函数异常的时候,它负责清理已申请的内存)。

其他知识点


std::new_handler

这是一个回调函数,主要用于 operator new申请内存不成功的时候调用,感觉可以类比于,信号处理函数,operator new申请不成功(接受到某个信号),调用该handler(触发信号处理函数)。最主要的用途就是通过set_new_handler()来更换不同的处理函数。

类继承中的operator new函数处理

假如我们为一个Base类,定制了自己的operator new,则Base的派生类Derived,肯定也继承了该函数,

Derived * dptr = new Derived;的时候,肯定就调用了Base::operator new()函数,而派生类的大小一般和基类大小是不同的,因此这里需要额外注意,不能调用基类的operator new();

对于这点解决办法,1,在Derived 中重写operator new函数;2,在Base类的operator new函数中添加一句话如下图:

void * operator new(size_t size ){
if(size != sizeof(Base))
return ::operator new(size);
/*其他交给Base::operator new处理*/
}

operator new[] 和 operator delete[]

这两个和operator new operator delete 用处基本一致,只是针对数组的,这里就不多讲。

额外的补充(可能有点绕口, 下面是纯个人观点,有哪里不对,请大家指出,共同学习)

void operator new(size_t size, void * ptr) noexcept; 这是C++11官方定义的placement new的格式,

该函数的主要作用是把把size大小的结构体放置在ptr指向的内存上,也就是在ptr指向的内存调用构造函数。

先说一个上面的正确的使用方法:

#include <iostream>
#include <new>
#include <cstring>
using namespace std;
class Base{
public:
Base():x(-1), y(-1){ cout<<"Construct of Base and my addr = : ["<<this<<"]."<<endl; }
static void * operator new(size_t size, void * ptr){
cout<<"My placement new."<<endl;
return ::operator new(size, ptr);
}
//Base类只占用两个int型的大小
int x;
int y;
};
static int arr[20]; //栈上的空间,用来分配Base
int main(){
memset(arr, 0, sizeof(arr));
cout<<"arr addr = ["<<arr<<"]."<<endl;
Base * ptr = new(arr) Base();
ptr = nullptr;
cout<<arr[0]<<" "<<arr[1]<<endl;
return 0;
}

arr数组前两个变成 -1。

上面的代码,ptr没有直接delete,而是直接赋值为nullptr,为什么这么做,因为调用delete ptr; 会直接错误,ptr指向的是栈地址空间,而delete释放的是堆上的空间,因此会出错。

另一种情况:假设arr就是堆上的地址空间,此时调用delete ptr,肯定能成功,但是这里会有两种风险,一种是,delete ptr;释放一次内存,delete arr;第二次释放内存,错误;另一种,假设ptr 是占用的arr指向的内存的中间部分,你delete ptr;归还给系统,但是 arr 这个时候该怎么处理,这种情况应该是坚决杜绝的,(具体情况我没测试,原理上肯定是杜绝这种情况出现:只归还堆上连续空间的中间部分。)

上面说了那么多,其实想表达一个意思,当调用 void * operator new(size_t size, void * ptr);无论其是否成功,或者接下来的构造函数是否成功,ptr内存都不应该释放,应该交由,ptr诞生的地方来管理。因此对于该函数一般不需要申明对应的 operator delete() 来防止构造函数未成功时候来释放内存(和“声明placement new 的时候一定要声明对应的 placement delete函数这个理论有点相反”)。

要是强行为 void * operator new(size_t size, void * ptr); 声明对应的operator delete ,方式如下:

void operator delete(void * ptr, void * ptr2);代码如下:

#include <iostream>
#include <new>
#include <cstring>
using namespace std;
class Base{
public:
Base():x(-1), y(-1){
cout<<"Construct of Base and my addr = : ["<<this<<"]."<<endl;
throw 2;
}
static void * operator new(size_t size, void * ptr){
cout<<"My placement new."<<endl;
return ::operator new(size, ptr);
}
/*
必须声明为 void * ptr1, void * ptr2 这种形式,声明为其他参数,和上面的 operator new不匹配
*/
static void operator delete(void * ptr1, void * ptr2){
cout<<"My operator delete"<<endl;
}
int x;
int y;
};
int arr[20];
int main(){
try{
memset(arr, 0, sizeof(arr));
cout<<"arr addr = ["<<arr<<"]."<<endl;
Base * ptr = new(arr) Base();
ptr = nullptr;
cout<<arr[0]<<" "<<arr[1]<<endl;
}
catch(...){
cout<<"catch one exception."<<endl;
}
return 0;
}

可以看出图中有 My operator delete 的显示,若把 operator delete参数改成其他的,则无法调用。

看到有地方把operator new(size_t size, int flag, ...);形式(也就是除了size_t 一个参数之外,还有自定义参数,也就是重载原来的operator new(size_t size); 都称为 placement new)我觉重载更直观。

对于void * operator new(size_t size, int flag, ../*自定义参数*/.. ); 与之对应的operator delete如下:

void operator delete(size_t size, int flag, ../*自定义参数*/..);

也就是有上面的为啥,operator delete(void * ptr1, void * ptr2);

上面的内容主要参考自《Effective C++》,若哪里有理解不对,请大家多指出。

定制自己的new和delete:operator new 和 operator delete的更多相关文章

  1. Effective C++ 第二版 8) 写operator new 和operator delete 9) 避免隐藏标准形式的new

    条款8 写operator new 和operator delete 时要遵循常规 重写operator new时, 函数提供的行为要和系统缺省的operator new一致: 1)正确的返回值; 2 ...

  2. ZT 自定义operator new与operator delete的使用(1)

    http://blog.csdn.net/waken_ma/article/details/4004972 先转两篇文章: 拨开自定义operator new与operator delete的迷雾 C ...

  3. 从零开始学C++之重载 operator new 和 operator delete 实现一个简单内存泄漏跟踪器

    先来说下实现思路:可以实现一个Trace类,调用 operator new 的时候就将指向分配内存的指针.当前文件.当前行等信息添加进Trace 成员map容器内,在调用operator delete ...

  4. operator new和operator delete

    从STL源码剖析中看到了operator new的使用 template<class T> inline void _deallocate(T* buffer) { ::operator ...

  5. C++面向对象高级编程(九)Reference与重载operator new和operator delete

    摘要: 技术在于交流.沟通,转载请注明出处并保持作品的完整性. 一 Reference 引用:之前提及过,他的主要作用就是取别名,与指针很相似,实现也是基于指针. 1.引用必须有初值,且不能引用nul ...

  6. operator new 和 operator delete 实现一个简单内存泄漏跟踪器

    先来说下实现思路:可以实现一个Trace类,调用 operator new 的时候就将指向分配内存的指针.当前文件.当前行等信息添加进Trace 成员map容器内,在调用operator delete ...

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

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

  8. 类的operator new与operator delete的重载【转】

    http://www.cnblogs.com/luxiaoxun/archive/2012/08/11/2633423.html 为什么有必要写自己的operator new和operator del ...

  9. 条款八: 写operator new和operator delete时要遵循常规

    自己重写operator new时(条款10解释了为什么有时要重写它),很重要的一点是函数提供的行为要和系统缺省的operator new一致.实际做起来也就是:要有正确的返回值:可用内存不够时要调用 ...

随机推荐

  1. BZOJ 4764: 弹飞大爷

    4764: 弹飞大爷 Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 4  Solved: 4[Submit][Status][Discuss] Des ...

  2. 【bzoj4537】 Hnoi2016—最小公倍数

    http://www.lydsy.com/JudgeOnline/problem.php?id=4537 (题目链接) 题意 给出一个${n}$个点${m}$条边的无向图,每条边有两个权值${a,b} ...

  3. 浅谈使用NIO,AIO的感受

    花了十多天的时间把原来的WEB服务由BIO(阻塞IO)模式改写成NIO(非阻塞IO)模式,然后在xp机子上用ab测试并发性能,确实提升了30%左右的并发性能,测试完成后,当时感觉还是挺满意的.几天前在 ...

  4. Build CRUD Application with jQuery EasyUI

    http://www.jeasyui.com/tutorial/app/crud.php It has become a common necessily for web application to ...

  5. SQL Server 2016 的JSON功能

    测试一下基本的,从查询结果里面构造一个json 的格式 create table t1(ID int identity,name nvarchar(50),Chinese int ,Math int) ...

  6. 20181108 Apache Commons Lang

    工具类 org.apache.commons.lang3 AnnotationUtils ArchUtils ArrayUtils BooleanUtils CharSetUtils CharUtil ...

  7. Hadoop生态圈-使用Kafka命令在Zookeeper中对应关系

    Hadoop生态圈-使用Kafka命令在Zookeeper中对应关系 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.zookeeper保存kafka的目录     二.使用Ka ...

  8. 均方根值(RMS)+ 均方根误差(RMSE)+标准差(Standard Deviation)

    均方根值(RMS)+ 均方根误差(RMSE)+标准差(Standard Deviation)  1.均方根值(RMS)也称作为效值,它的计算方法是先平方.再平均.然后开方. 2.均方根误差,它是观测值 ...

  9. html5 canvas裁剪区域

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. 2016-2017-2 20155309 南皓芯java第六周学习总结

    教材内容详解 这一次主要学习的是第十章与第十一章的内容.主要讲述了串流,字符处理和线程以及并行API. 输入输出 串流:Java中的数据有来源(source)和目的地(destination),衔接两 ...