int * aptr = new int(10);
delete aptr, aptr = nullptr;ios
上面的代码是咱们最基本也是最多见的使用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 deleteide
全局默认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学习
#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);这个函数this
当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 ???由于它的用途不在这,而在于下面将要讲的。spa
#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 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的构造函数中,抛出一个异常(忽略什么异常,主要用来模拟),直接上运行结果图:
code
如上图所示的运行结果,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);orm
如果不给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);的主要用途,当构造函数异常的时候,它负责清理已申请的内存)。
这是一个回调函数,主要用于 operator new申请内存不成功的时候调用,感受能够类比于,信号处理函数,operator new申请不成功(接受到某个信号),调用该handler(触发信号处理函数)。最主要的用途就是经过set_new_handler()来更换不一样的处理函数。
假如咱们为一个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 用处基本一致,只是针对数组的,这里就很少讲。
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++》,若哪里有理解不对,请你们多指出。