先简单解释下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++,里面讲的很详细。