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. 一本通1640C Looooops

    1640:C Looooops 时间限制: 1000 ms         内存限制: 524288 KB [题目描述] 原题来自:CTU Open 2004 对于 C 语言的 for (variab ...

  2. C#解析数组形式的json数据

    在学习时遇到把解析json数据的问题,网上也搜了很多资料才得以实现,记录下来以便翻阅. 1. 下载开源的类库Newtonsoft.Json(下载地址http://json.codeplex.com/, ...

  3. python_面向对象魔法方法指南

    原文: http://www.rafekettler.com/magicmethods.html 原作者: Rafe Kettler 翻译: hit9 原版(英文版) Repo: https://gi ...

  4. Java 基本数据类型 sizeof 功能

    Java 基本数据类型 sizeof 功能 来源 https://blog.csdn.net/ithomer/article/details/7310008 Java基本数据类型int     32b ...

  5. BZOJ 4004: [JLOI2015]装备购买

    4004: [JLOI2015]装备购买 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 1154  Solved: 376[Submit][Statu ...

  6. 51nod1134——(最长上升子序列)

    给出长度为N的数组,找出这个数组的最长递增子序列.(递增子序列是指,子序列的元素是递增的) 例如:5 1 6 8 2 4 5 10,最长递增子序列是1 2 4 5 10.   Input 第1行:1个 ...

  7. QWidget窗体中使用Q_OBJECT后无法添加背景图片或背景色

    在继承自QWiget的窗体中,设置背景图片或背景色比较简单的方法是使用setStyleSheet()函数,比如在构造函数中可以这样来设置背景图片: this->setStyleSheet(&qu ...

  8. 《剑指offer》— JavaScript(26)二叉搜索树与双向链表

    二叉搜索树与双向链表 题目描述 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表.要求不能创建任何新的结点,只能调整树中结点指针的指向. 思路 递归思想:把大问题转换为若干小问题: 由于Ja ...

  9. 24. Swap Nodes in Pairs(M);25. Reverse Nodes in k-Group(H)

    24. Swap Nodes in Pairs Given a linked list, swap every two adjacent nodes and return its head. For ...

  10. RabbitMQ服务主机名更改导致消息队列无法连接

    RabbitMQ服务主机名更改导致消息队列无法连接 在多节点环境中,RabbitMQ服务使用一个独立节点部署.在此环境下,如果修改了RabbitMQ节点的主机名,则需要更新RabbitMQ用户才能保证 ...