探讨operator new和new operator
1. 基础知识
- new operator:指我们在C++里通常用到的关键字,比如A* a = new A
- operator new:它是一个操作符,并且可被重载(类似加减乘除的操作符重载)
对于如下一个表达式,就是new operator的一个简单的例子。
A* a = new A;
其具体过程分为如下三步。
1.分配内存,2.调用构造函数构造对象,3. 返回分配指针。
对于分配内存这一操作就是由operator new来完成的,然后会根据参数来选择合适的构造函数,如果不加参数则是调用默认构造函数(如上例子)
2. operator new的三种形式
#include <new>
//C++98版本
//throwing (1)
void* operator new (std::size_t size) throw (std::bad_alloc);
//nothrow (2)
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
//placement (3)
void* operator new (std::size_t size, void* ptr) throw()
//C++11版本
//throwing (1)
void* operator new (std::size_t size);
//nothrow (2)
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
//placement (3)
void* operator new (std::size_t size, void* ptr) noexcept;
首先要了解到C++给我们提供了global operator new 操作,就是如上的三种形式,默认情况下,我们一般都是调用的这三种operator new操作之一。C++11和C++98的区别只是在于关键字noexcept的使用。
(1)(2)的区别仅是是否抛出异常,当分配失败时,前者会抛出bad_alloc异常,后者返回null,不会抛出异常。它们都分配一个固定大小的连续内存。
(3)是placement new,它也是对operator new的一个重载,定义于#include <new>
中,它多接收一个ptr参数,但它只是简单地返回ptr。然后后续就在ptr所指地址上构建一个对象(通过调用其构造函数),这在内存池技术上有广泛应用。
以C++11版本为例,三种global operator new的应用如下:
A *p1 = new A; //使用第一种operator new
A *p2 = new(std::nothrow) A; //使用第二种operator new
void *p3 = malloc(sizeof(A)); //申请内存
A *p3 = new(p3) A; //使用第三种operator new
如new(p) A()
,就是在p所指定的地址上调用A的构造函数。这是的p即可是堆中的地址,也可以是栈中的地址。
3. operator new重载
可以根据自己的需要来在相应的类中重载operator new,此时在new关键字作用在这种类型时候,将首先调用类内重载的operator new操作,而不是全局函数 operator new。
4. delete
delete的使用基本和new一致。
delete的具体
包括operator delete的重载方式这些都相似,只不过它的参数是void*,返回值为void。
5. set_new_handler
当operator new申请一个内存失败的时候,它会进行如下的处理步骤:
1. 如果存在客户指定的处理函数,则调用处理函数(new_handler),如果不存在则抛出一个异常。
2. 继续申请内存分配请求。
3. 判断申请内存是否成功,如果成功则返回内存指针,如果失败转向处理步骤1
内存处理例程通过set_new_handler来进行设置
namespace std{
typedef void (*new_handler)();
//无异常抛出
new_handler set_new_handler(new_handler p) throw();
}
其中new_handler是个typedef,定义一个函数指针,该函数没有参数,也没有返回值;set_new_handler用于设置处理函数,设置p为当前处理函数,并返回之前的new_handler。
默认情况下,系统并不会设置new_handeler,为0,而是交给客户端来自行设计。当没有new_handler时候,就会直接抛出异常或者结束。如果存在new_handler函数,就会先调用new_handler,函数结束后再直接申请内存,如果仍然无法申请,继续调用new_handler,直到成功。
一个设计良好的new_handler必须做以下事情:
- 删除其它无用的内存,使系统具有可以更多的内存可以使用,为下一步的内存申请作准备。实现此策略的办法是:程序一开始执行就分配一大块内存,当new_handler被调用时,将它们释放还给程序使用。
- 设置另外一个new_handler。如果当前的new_handler不能够做到更多的内存申请操作,或者它知道另外一个new_handler可以做到,则可以调用set_new_handler函数设置另外一个new_handler,这样在operator new下一次调用的时候,可以使用这个新的new_handler。
- 卸载new_handler,使operator new在下一次调用的时候,因为new_handler为空抛出内存申请异常。
- new_handler抛出自定义的异常
- 不再返回,调用abort或者exit退出程序
我们可以直接在一个函数中调用set_new_handler,来设定相应的内存不足处理函数,但是这并不是针对某个类的,而是所有的operator new调用失败。
但是如果需要,可以重载operator new,自己实现这个行为。
需要重载相应类的operator new来进行设定:
- 首先调用标准的set_new_handler,自定义专属类的处理函数
- 调用::operator new,执行实际的内存分配。如果内存分配失败,刚才被安装的new_handler将被调用。
3.无论new成功还是失败,都必须在类自定义的operator new结束前恢复全局new_handler