C++的三种new简介及重载局部(类内部)与全局operator new

先简单解释下C++的三种new,由于它们的名字实在是。。我就说的通俗点。1、new运算符(new operator)大哥,我们在代码中直接使用的就是它。它做2件事:调用后两种new申请内存和初始化对象。2、operator new,是一个函数,所以也是三种new唯一一个可以重载的。它类似C语言中的malloc,用于分配内存。3、placement new,用于初始化对象(如果有的话,就是它调用构造函数)。它还有其他的一些用法,下面具体介绍的时候再说。

1)重载全局operator 

先看代码:

void * operator new(size_t size)
{
cout << "内存分配";
return malloc(size);
}


int main()
{
int * p = new int();
cin.get();
return 0;
}

输出:内存分配

        这样貌似是重载了operator new,可能你还在怀疑就这么几行代码,仅仅只是返回一片内存块,用它替换原先的operator new是否能正确完成一个对象的构造。答案是可以。你可以尝试new一个对象试试,不会有任何问题。要记住,三种new 各自分工,它唯一需要做的只是分配内存,只要内存分配对了,一切OK。

        2)重载局部(类内部)operator new

还是先看代码:

class A
{
public :
A()
{
cout << "对象构造\n";
}
void * operator new(size_t size)
{
cout << "内存分配\n";
return malloc(size);
}
};


int main()
{
A *p = new A();
cin.get();
return 0;
}

先后输出:内存分配,对象构造。

        1、这是类内部的重载,就像重载运算符一样,仅对于这个类有效。所以如果你像这样int *p = new int();不会输出"内存分配"。此时new 会去调用全局的operator new。
        2、第一点说过,这个operator new仅在类内部有效,所以它和new运算符所调用的全局operator new是2个不同的函数。所以如果你把上面代码中的  return malloc(size);  改成  return ::operator new(size);  可以达到同样的效果。因为同样只是完成内存的分配,这里把malloc换成全局的operator new成为纯C++的语法,或许更严谨一些吧。

        3、可能有人会想,既然可以把malloc换成全局的operator new,能不能直接把return malloc(size);  改成  return ::new A();  这个就有意思了,既然类重载的operator new只有可以看作是劫持全局operator new的功能,何不直接分配内存也不让它管了,劫持之后剩下的工作让全局new和全局operator new去完成。

    所以调用顺序是这样的:new运算符 -> 重载operator new -> new运算符 -> 全局operator new -> 第二个new运算符调用的placement new -> 第一个new运算符调用的int i

    看起来有些复杂,输出结果是这样的,内存分配,对象构造,对象构造。

    没错,虽然在类重载的operator new里输出了分配内存,但实际上只有全局operator new真正去做分配内存。然后2次调用的运算符new调用了2次的placement new。但是这样做目前为止到没发现什么问题,但实际上对象构造了2次,实在是没有什么实际意义,而且在某些情况下可能会引起严重的错误。比如在构造函数里做一些其它的内存分配工作。

      4、可能又有人想了,能不能把重载全局operator new中的return malloc(size);  改成  return ::operator new(size);   这是不行的,因为此时全局的operator new就是你重载了的operator new了,这样改写运行程序,你将会看到无数的 "内存分配"疯狂的输出,没错,它递归调用自身了。这就跟把重载类operator new中的return malloc(size);  改成  return operator new(size);一样,你是在让它调用自己。

        再简单介绍一下placement new,前面说过,它是在operator new分配的内存上初始化这片区域,调用构造函数。我们也可以直接来使用它,你手上有一块内存p。int *p2 = new (p) int(100);  这样你就用placement new把一块内存初始化为int,并给它个初值100。其实这里,p所指向的内存可以是堆也可以是栈。如果是栈内存被这样new,不需要delete,且可以反复的new。这有什么用, 你可以把动态分配内存的请求限制在一块区域内,这就有点像操作系统给引用程序分配内存,这一块给你随便用, 妨碍不到其他的内存。算是一种安全策略吧。

           最后再说一下:

operator new 会多申请sizeof(int)大小的内存用来保存这段空间的大小。

placement new 如果在申请一个数据的时候也要多申请sizeof(int)大小的空间用来保存数组的大小。eg,

void *ptr = operatro new(sizeof(int)*100);

int *iPtr = new(ptr)int[100];//如果这样,那么这里将会出错的,因为你实际上系统是申请了100+sizeof(int)的空间!然而ptr上面只有100*szieof(int)的大小,所以申请空间失败。

上述二者都是new运算符在调用operator new 时传参数size自动为我们加上的,我们在重载operator new以及使用new时不需要担心这个问题。

         另外,本文中没有提到delete,delete[]以及new []重载,new []的重载就不赘述了。至于delete,三种new其实也对应着不同的三种delete:new运算符对应delete,operator new和placement new处理的内存都可以使用operator delete来释放,但是释放之前则需要我们手动调用析构函数。因为调用析构函数是delete运算符帮我们调用的,不直接使用delete的话则需要手动调用析构函数。

        其实重载operator new的目的并不是我们需要多么高级的内存分配或对象构造技术,而是有时我们需要跟踪内存的分配,给它劫持一下,做个程序计数什么的,你甚至可以用重载,做一个内存管理器,Java或.NET的垃圾回收器那种听起来很高大上的东西,尤其是在内存管理这一块,在面试的时候跟HR吹一把能给你加不少分。

       很久以前学的知识(趁我忘了之前赶紧写进博客),如果有什么地方不对,欢迎各位牛人批评指教,想要深入研究的朋友可以去读一下more effective C++,里面讲的很详细。

--------------------- 

作者:Koma丶 
来源:CSDN 
原文:https://blog.csdn.net/qq_29227939/article/details/51638241 
版权声明:本文为博主原创文章,转载请附上博文链接!

==================================

new运算符重载

==================================

首先我们要清楚,为什么我们要重载new,和delete了?这还不是指针造成的,确实指针是一件让人喜欢的东西,用起来如此让人喜欢,让人顺手。然而小程序我们完全可以避免内存泄露问题,大程序就不那么容易了,然而我们有一种特别好的方法可以跟踪我们new,和delete动作,找到未被释放的内存。办法是什么呢?微软给我们提供了一种很好的方法,那就是重载new,和delete。

       在实现之前我们要清楚new,和delete的工作机理,这是个多么好的学习机会啊!

       对与new操作符,其实和sizeof一样,都是c++内置的,然而像strlen就不是了,strlen属于函数。对于new的功能我们是没有办法改变的,当我们new一个对象时,new为我们做了两件事情,一、申请一块足够的内存空间供存放对象,对于new一个数组对象,编译器会计算出总共的空间,然后执行类似c语言中malloc函数类似的功能。二、初始化对象,对于单个对象,包括包括基本对象和类对象,可以通过括号初始化,比如int * pn = new int(3); A * pa = new A(3);   然而对于数组不能初始化,对于类对象,必须定义无参数的构造函数。对与new的基本功能我们是无法改变的。

       我们所能改变的就是如何为对象分配内存,我们可一重载这个函数以实现分配内存。通常的重载方式是:

       void * operator new (size_t size);

       然而这已经足够了,在一般的operator new 重载函数里,我们可以再加入其它参数,但第一个参数必须是size_t类型,即为无符号整形。正是有这种特性,为我们对内存申请和释放的跟踪提供了可能。


       具体实现就是第一个operator new 的重载函数,我们第一的这个函数是这样的:

void * operator new(unsigned int size, const char *file, int line)
{
    cout << "new size:" << size << endl;
    cout << file << " " << line << endl;

    void * p = malloc(size);

    return p;
}

       然后用宏替换所有的new:

#define new new(__FILE__, __LINE__)

       这样我们每次调用new,比如int * pn = new int;被编译器替换成了int * pn = new (__FILE__, __LINE__) int,从而调用我们定义的operator new,这种办法确实很妙。需要交代的是,对于数组同样适用,而是在编译的是后由编译器计算出所需要的长度调用我们定义的operator new函数。

       对于delete,我们无需使用宏定义,只要重载operator delete就可以了,然而我们需要重载两个delete:

void operator delete(void * p)
{
    cout << "delete " << (int)p << endl;
    free(p);
}

void operator delete [] (void * p)
{
    cout << "delete [] " << (int)p << endl;
    free(p);
}


       其实后面一个函数的内容和前面的内容一样,但为了支持数组的释放,我们必须是要定义的。那么我们只是简单的free(p)编译器咋知道我们释放的数组,还是单个对象了,这个不用我们操心了,我们只要提供给free函数一个申请内存的首地址就可以了。因为在用malloc申请的时候,我们也把数组装换为内存大小了。

       由此我们可以得出下面的推断:用int * pn = new int [100];的空间,用delete pn释放和delete [] pn释放效果一样。然而那么既然这两种方式一样,那还要delete [] 干什么,其实delete [] 有另外的作用就是类对象数组的释放,编译器把delete []解释为调用对象的析构函数,这个是通过循环实现的,最后释放掉申请的内存。

       既然我们已经跟踪了new和delete,那么就可以比较容易的判断申请的内存是否最后得到释放,要完成它,我们还需要一个链表,或者其它,当我们申请一块内存的时候加入到链表中,释放一块空间的时候,从链表中删除和释放内存首地址相同的节点就可以了,最后理想的情况是链表为空,如果不为空,那就说明内存发生泄露(Memory leaks)了。

完整代码:

#include "malloc.h"
#include "iostream.h"

#ifdef _DEBUG

void * operator new(unsigned int size, const char *file, int line)
{
    cout << "new size:" << size << endl;
    cout << file << " " << line << endl;

    // 下面两种方法可以达到同样的效果,但下面一种比较好
    // 因为用下面一种可以保持原有的申请方式一样
    //void * p = malloc(size);
    void * p = operator new(size);

    return p;
}

void operator delete(void * p)
{
    cout << "delete " << (int)p << endl;
    free(p);
}

void operator delete [] (void * p)
{
    cout << "delete [] " << (int)p << endl;
    free(p);
}

void operator delete(void * p, const char *file, int line)
{
    cout << "delete file line" << endl;
    free(p);
}

void operator delete [] (void * p, const char *file, int line)
{
    cout << "delete [] file line" << endl;
    free(p);
}

#define new new(__FILE__, __LINE__)
#endif

void main()
--------------------- 
作者:ghevinn 
来源:CSDN 
原文:https://blog.csdn.net/ghevinn/article/details/18359519 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/mw_nice/article/details/83545104
new