http://blog.csdn.net/waken_ma/article/details/4004972

先转两篇文章:

拨开自定义operator new与operator delete的迷雾

C++允许用户通过自定义operator new和operator delete的方式来更改new与delete表达式的某些行为,这给了程序员定制内存管理方案的自由。但是享受这种自由的时候必须遵守一定的规范,具体可以参见《Effective C++ 2nd》的相关条款。本文补充解释一些特别容易引起误解的问题。

operator new和operator delete都有其正规形式(normal signature):

void* operator new(size_t size);
void operator delete(void *p);
void operator delete(void *p,size_t size);

数组版本则是:

void* operator new[](size_t size);
void operator delete[](void *p);
void operator delete[](void *p,size_t size);

以下对operator new和operator delete的讨论,如无特别说明,同样也适合于它们的数组版本。

普通的new与delete表达式在分配与释放内存时调用的就是这些正规形式的版本。operator delete(void *p,size_t size)中的第二个参数由系统自动传递,用来表明指针所指对象的实际大小。一般来说operator

delete(void*)的优先级比operator delete(void*,size_t)要高,这意味着如果在同一空间(scope)定义了这两种形式的delete,拥有单一参数者优先被编译器选择。这一点在VC7.1中得到验证,不知其它编译器如何?

除了上面的正规形式外,我们还可以定义拥有更多参数的operator new和operator delete,只要保证前者的返回值和第一个参数分别是void*和size_t类型,而后者的分别是void和void*就行了。比如:

void* operator new(size_t size,const char* szFile,int nLine);
void operator delete(void *p,const char*,int);

表达式new("xxx",20) SomeClass实际上就是告诉编译器调用上面的多参数operator new来分配内存。但是不要依此类推出 delete("xxx",20) pObj,这是非法的。那么怎么才能调用到这个多参数的operator delete呢?实话告诉你,你没有这个权利。呵呵,别吃惊,容我慢慢解释。当两个operator new和operator delete有相等的参数个数,并且除了第一个参数之外其余参数的类型依次完全相同之时,我们称它们为一对匹配的operator new和operator delete。按照这个标准,上面两位就是匹配的一对了。在我们使用 SomeClass *pObj = new("xxx",20) SomeClass 于堆中构建一个对象的过程中,如果在执行SomeClass的构造函数时发生了异常,并且这个异常被捕获了,那么C++的异常处理机制就会自动用与被使用的operator new匹配的operator delete来释放内存(补充一点:在operator new中抛出异常不会导致这样的动作,因为系统认为这标志着内存分配失败)。编译期间编译器按照以下顺序寻找匹配者:首先在被构建对象类的类域中寻找,然后到父类域中,最后到全局域,此过程中一旦找到即停止搜寻并用它来生成正确的内存释放代码,如果没有找到,当发生上述异常情况时将不会有代码用来释放分配的内存,这就造成内存泄漏了。而如果一切正常,delete pObj 则总是会去调用operator delete的正规形式。现在明白了吧,多参数的operator delete不是给我们而是给系统调用的,它平常默默无闻,但在最危急的关头却能挺身而出,保证程序的健壮性。为了有个感性的认识,让我们看看下面的代码(试验环境是VC7.1):

#include <malloc.h>

struct Base
{
    Base()
    {
        throw int(3);
    }

~Base() {}

void* operator new( size_t nSize, const char*,int)
    {
        void* p = malloc( nSize );

return p;
    }

void operator delete( void *p)
    {
        free(p);
    }

void operator delete( void* p,const char*,int)
    {
        free( p );
    }
};

#define NULL 0
#define new new(__FILE__, __LINE__)

int main( void )
{
    Base* p = NULL;

try
    {
        p = new Base;

delete p;
    }
    catch(...)
    {
    }

return 0;
}

跟踪执行会发现:程序在 p = new Base 处抛出一个异常后马上跳去执行operator delete(void*,const char*,int)。注释掉Base构造函数中的throw int(3)重来一遍,则new成功,然后执行delete p,这时实际调用的是Base::operator delete(void*)。以上试验结果符合我们的预期。注意,operator new和operator delete是可以被继承和重定义的,那接下来就看看它们在继承体系中的表现。引进一个Base的派生类(代码加在#define NULL 0的前面):

struct Son : public Base
{
    Son()
    {
    }

void* operator new( size_t nSize, const char*,int)
    {
        // class Son

void* p = malloc( nSize );
        return p;
    }

void operator delete( void *p)
    {
        // class Son
        free(p);
    }

void operator delete( void* p,const char*,int)
    {
        // class Son
        free( p );
    }
};
    然后将main函数中的p = new Base改成p = new Son并且取消对Base()中的throw int(3)的注释,跟踪执行,发现这回new表达式调用的是Son重定义的operator new,抛出异常后也迅速进入了正确的operator delete,即Son重定义的多参数版本。一切都如所料,是吗?呵呵,别急着下结论,让我们把抛异常的语句注释掉再跑一次吧。很明显,有些不对劲。这次delete p没有如我们所愿去调用Son::operator delete(void*),而是找到了在Base中定义的版本。怎么回事?我愿意留一分钟让好奇的你仔细想想。

找到答案了吗?没错,罪魁祸首就是那愚蠢的Base析构函数声明。作为一个领导着派生类的基类,析构函数竟然不声明成virtual函数,这简直就是渎职。赶紧纠正,在~Base()前加上一个神圣的virtual,rebuild and run.....。谢天谢地,世界终于完美了。

可能你会疑惑,在没有给基类析构函数加virtual之前,当发生异常时C++为什么知道正确地调用派生类定义的多参数operator delete,而不是基类的?其实很简单,new一个对象时必须提供此对象的确切类型,所以编译器能够在编译期确定new表达式抛出异常后应该调用哪个类定义的operator delete。对于正常的delete p来说,如果p被声明为非基类类型的指针,编译器就会在编译时决定调用这种声明类型定义的operator delete(静态绑定),而如果p是某种基类类型指针,编译器就会聪明地把到底调用哪个类定义的operator delete留待运行期决定(动态绑定)。那么编译器如何判断p是否是基类指针呢?实际上它的根据就是p的声明类型中定义的析构函数,只有在析构函数是虚拟的情况下p才被看成基类指针。这就可以解释上面碰到的问题。当时p被声明为Base*,程序中它实际指向一个Son对象,但我们并没有把~Base()声明为虚拟的,所以编译器大胆地帮我们做了静态绑定,也即生成调用Base::operator delete(void*)的代码。不过千万不要以为所有编译器都会这样做,以上分析仅仅是基于VC7.1在本次试验中的表现。事实上在C++标准中,经由一个基类指针删除一个派生类对象,而此基类却有一个非虚拟的析构函数,结果未定义。明白了吧老兄,编译器在这种情况下没有生成引爆你电脑的代码已经算是相当客气与负责了。现在你该能够体会Scott Meyers劝你"总是让base class拥有virtual detructor"时的苦心吧。

最后要指出的是,试验代码中对operator new和operator delete的实现相当不规范,负责任的做法仍然请大家参考Scott Meyers的著作。

operator new和operator delete学习总结

operator new和operator delete学习总结
这块内容很多很多,那就捡几个重要的地方说说吧,主要目的是为了在遗忘的时候给自己提个醒,呵呵。
1。new和delete的重载函数都是static函数,你可以在声明的时候写上去,也可以不写(编译器自动为你添加),但是事实是无法更改的,它们都是static的。
2。new和delete必须形式上配对,且里面做的内容也要配对
void* operator new(size_t size, const char*, int);//调用形式为new("wokao",34) ;
void operator delete(void *p, const char*, int);//这个delete与上面的那个new配对
void operator delete(void *p, size_t, const char*, int)//这个delete与上面的那个new不配对
3。new和delete的标准形式是:
void* operator new(size_t size);
void operator delete(void* p);
或void operator delete(void* p,size_t size);//这个delete可以验证if( sizeof(X)!=size )
4。
new可以被重载出其他的样式来;而delete重载时,如果有其他样式出现,那是为了配合new的新样式,与此同时也必须得有标准的delete样式,
因为我们最后delete
*p的时候调用的是标准的delete样式,而那个非标准的只是为了在构造函数抛出异常时让系统调用与新样式的new配套的新样式的delete。
5。只有基类的析构函数被声明为virtual时(如果你没有在基类中写析构函数而是利用默认的或者即便写了却忘了加virtual都是不可以的),才可以通过基类的指针(实则指向子类)来delete,达到调用子类delete的目的。
6。不论你重载不重载,本质上new都是调用::operator new(size)这个全局函数来分配恰当的内存空间,可以在调用new之前通过set_new_handler函数来设置配置内存失败的回调函数。
7。new重载函数中的size至少是1,即便类没有任何成员变量。
8。new的顺序:new->基类的构造函数(在进入基类构造函数之前当然需要先根据初始化列表来初始化基类的部分成员变量)->子类的构造函数(在进入基类构造函数之前当然需要先根据初始化列表来初始化基类的部分成员变量)
9。delete的顺序:子类的析构函数->基类的析构函数->delete
10。重载new运算符时最好要让标准的new的调用形式露出来
void* operator new(size_t size,char*);void* operator new(size_t size);
或void* operator new(size_t size,char* =NULL);
11。写一个new,就最好写一个与之配套的delete。
12。对于new[]和delete[]的重载,也完全没啥区别,就是换了个名称,除此之外,再无其他特别之处了。

ZT 自定义operator new与operator delete的使用(1)的更多相关文章

  1. 定制自己的new和delete:operator new 和 operator delete

    new和delete不同用法 基本用法 int * aptr = new int(10); delete aptr, aptr = nullptr; 上面的代码是我们最基本也是最常见的使用new和de ...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  9. operator new和operator delete

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

随机推荐

  1. Mock单元测试

    /// <summary> /// 普通插入 /// </summary> [Fact] public void InsertOrder_Tests() { _sqlMappe ...

  2. vue组件中camelCased (驼峰式) 命名与 kebab-case(短横线命名)

    HTML 特性是不区分大小写的.所以,当使用的不是字符串模版,camelCased (驼峰式) 命名的 prop 需要转换为相对应的 kebab-case (短横线隔开式) 命名: 如果你使用字符串模 ...

  3. oracle 单实例DG(搭建篇一)

    一,介绍 lodding... 二,安装前环境配置 01,依赖包的安装: yum install binutils-* yum install compat-libstdc++-* yum insta ...

  4. Java基础21-构造函数之间的调用

    public class Test{ public static void main(String[] args){ Persion p2=new Persion("小明",22) ...

  5. Ace教你一步一步做Android新闻客户端(四) 优化Bitmap大法

    我计划着把需要用到的知识分解开来写,趁着我们要开发这款客户端的机会把安卓所有移动客户端开发中的技术贯穿其中,也是我自己成长的过程.By Ace in 20160121 我们开发一款新闻客户端程序,它的 ...

  6. Java中forEach, 用来遍历数组

    这里的for是Java中forEach, 用来遍历数组的.for(int i : d) 就是遍历int型数组d的 每一次访问数组d的时候读取的数据放入int型的i中.和for(int i=0;i< ...

  7. 在SourceTree中使用Git submodule

    在開發的過程中我們的項目可能會引用其他的版本庫中的代碼, 例如公司已經累積了一套公用的函式庫, 被多個項目調用;  很顯然地, 不能把公用函式庫的文件直接放到我們開發中的項目中, 這樣不但項目的冗餘, ...

  8. bzoj 5217: [Lydsy2017省队十连测]航海舰队

    Description Byteasar 组建了一支舰队!他们现在正在海洋上航行着.海洋可以抽象成一张n×m 的网格图,其中有些位置是" .",表示这一格是海水,可以通过:有些位置 ...

  9. Nginx的各种报错总结

    1.Nginx安装过程报错 错误一:软件依赖包未正确安装问题---PCRE依赖包没有安装 ./configure: error: the HTTP rewrite module requires th ...

  10. c# 判断是否是DICOM文件

    public bool isDicom(string filename) { FileStream fs = File.OpenRead(filename); ]; fs.Read(data, , d ...