C++ new与delete
C++ new与delete
new operator 和 delete operator
new operator 和delete operator 是运算符,
我们知道new运算符会干2件事:申请内存和调用对象构造函数,比如,当我们new一个string对象:
string *ps = new string("Hands up!")
编译器实际的工作大概是这样:
void *mem = operator new(sizeof(string)); //申请内存 call string::string("Hands up!") on *mem; // 调用构造函数 string *ps = static_cast<string*>(mem); // 返回string*指针
同样,delete运算符也会干2件事:调用对象析构函数和释放内存
delete ps
编译器实际的工作大概是这样:
ps->~string() operator delete(ps)
编译器看到类类型的new或者delete运算符的时候,首先查看该类是否是有operator new或者operator delete成员,如果类定义了自己的operator new和operator delete函数,则使用这些函数为对象分配和释放内存,否则调用标准库版本(:: operator new和:: operator delete)。
一个例子:
struct foo { foo(int x) {m = x; printf("foo(%d)\n", m);} ~foo() {printf("~foo()\n");} int get() {printf("m=%d\n", m);} static void* operator new(size_t size) { printf("operator new()\n"); return ::operator new(size); } static void operator delete(void* ptr) { printf("operator delete()\n"); ::operator delete(ptr); } private: int m; }; int main() { foo* f = new foo(10); f->get(); delete f; return 0; }
输出结果
operator new() foo(10) m=10 ~foo() operator delete()
可见,当调用new operator的时候,会先调用operator new()分配内存,然后调用构造函数;当调用delete operator的时候,先调用析构函数,然后调用operator delete()释放内存。
operator new() 和 operator delete()
operator new()和operator delete() 是函数,跟operator =() 类似。
表达式:
void *operator new(size_t); void *operator new[](size_t); void operator delete(void*); void operator delete[](void*);
前面提到,全局的::operator new() 内部其实就是调用malloc分配内存,而::operator delete() 内部就是调用free释放内存。
operator new() 和operator delete() 都可以重载。
重载operator new时:
- 第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t,此外,还可以带其它参数;
- 只分配需要的空间,但不调用构造函数;当无法满足所需内存空间时,如果有new_handler,则调用new_handler;
看一个带额外参数的operator new()的例子
struct foo { foo(int x) {m = x; printf("foo(%d)\n", m);} ~foo() {printf("~foo()\n");} int get() { printf("m=%d\n", m); } static void* operator new(size_t size, char* desc) { printf("operator new(%s)\n", desc); void* p = ::operator new(size); return p; } static void operator delete(void* ptr) { printf("operator delete()\n"); ::operator delete(ptr); } static void operator delete(void* ptr, char* desc) { printf("operator delete(%s)\n", desc); ::operator delete(ptr); } private: int m; }; int main() { //foo* e = new foo(10); // Error, foo* f = new("hello") foo(10); f->get(); delete f; return 0; }
输出结果:
operator new(hello) foo(10) m=10 ~foo() operator delete()
代码中,
foo* e = new foo(10) 会发生错误的原因是 member function(成员函数)的名字会覆盖外围的具有相同名字的函数。
delete f 调用的是operator delete()常规版本。
考虑一下这种情况,operator new()内存分配成功,但构造函数里面抛出异常,runtime system(运行时系统)有责任撤销 operator new() 所执行的分配。然而,runtime system并不能了解被调用的 operator new() 版本是如何工作的,所以它自己无法撤销那个分配。runtime system转而寻找一个和 operator new 持有相同数量和类型额外参数的 operator delete 版本,例如:
struct foo { foo(int x) { m = x; printf("foo(%d)\n", m); throw this; } ~foo() {printf("~foo()\n");} int get() { printf("m=%d\n", m); } static void* operator new(size_t size, char* desc) { printf("operator new(%s)\n", desc); void* p = ::operator new(size); return p; } static void operator delete(void* ptr) { printf("operator delete()\n"); ::operator delete(ptr); } static void operator delete(void* ptr, char* desc) { printf("operator delete(%s)\n", desc); ::operator delete(ptr); } private: int m; }; int main() { try { foo* f = new("hello") foo(10); f->get(); delete f; } catch(void* e) { printf("catch exception"); } return 0; }
输出:
operator new(hello) foo(10) operator delete(hello)
这时,带额外参数的operator delete被调用了,如果没有这个额外参数版本,就发生了内存泄露。
如上,规则很简单:对一个带有额外参数的 operator new(),必须既提供常规 operator delete(用于构造过程中没有抛出 exception(异常)时),又要提供一个持有与 operator new 相同的 额外参数的版本(用于构造有异常的情况)。
再看下operator new[]和operator delete[]的例子:
struct foo { foo() {printf("foo()\n");} foo(int x) { m = x; printf("foo(%d)\n", m); } ~foo() {printf("~foo()\n");} int get() { printf("m=%d\n", m); } static void* operator new(size_t size) { printf("operator new(%d)\n", size); void* p = ::operator new(size); return p; } static void* operator new[](size_t size) { printf("operator new[](%d)\n", size); void* p = ::operator new[](size); return p; } static void operator delete(void* ptr, size_t size) { printf("operator delete(%d)\n", size); ::operator delete(ptr); } static void operator delete[](void* ptr, size_t size) { printf("operator delete[](%d)\n", size); ::operator delete[](ptr); } private: int m; }; int main() { const int len = 3; foo* f = new foo[len](); for (int n=0; n<len; n++) { f[n].get(); } delete []f; return 0; }
new 失败的处理
operator new 在空间不足导致内存分配失败时:
- 调用全局的new_handler 类型的函数,这个函数的默认处理是抛出std::bad_alloc异常;
- 可以通过 set_new_handler(void(*new_handler)) 自定义new_handler函数,具体实现一般是内存碎片整理;
- 当operator new不能满足一个内存请求的时候,它会反复调用new_handler函数直到它发现有足够的内存可以分配;
设置全局的new_handler
void my_new_handler() { cout << "No enough memory" << endl; exit(1); } int main() { set_new_handler(my_new_handler); int *pBigDataArray = new int[100000000L]; delete [] pBigDataArray; }
如果operaotr new无法为100,000,000个整数分配内存,就会调用outOfMem,也就是输出一个error信息之后程序终止(exit调用);
与operator new的重载类似,我们也可以为某个类自定义new_handler, 从而代替全局的new_handler,例如
void my_new_handler() { cout << "No enough memory" << endl; exit(1); } class NewHandlerHolder { public: explicit NewHandlerHolder(std::new_handler nh): handler(nh) {} // new-handler ~NewHandlerHolder() { std::set_new_handler(handler); } private: std::new_handler handler; NewHandlerHolder(const NewHandlerHolder &) =delete; NewHandlerHolder& operator=(const NewHandlerHolder &) =delete; }; class Widget { public: int data; static std::new_handler set_new_handler(std::new_handler p) throw(); static void *operator new(std::size_t size) throw(std::bad_alloc); private: static std::new_handler currentHandler; }; std::new_handler Widget::currentHandler = NULL; std::new_handler Widget::set_new_handler(std::new_handler p) throw() { std::new_handler oldHandler = currentHandler; currentHandler = p; return oldHandler; } void *Widget::operator new(std::size_t size) throw(std::bad_alloc) { NewHandlerHolder h(std::set_new_handler(currentHandler)); // install Widget’s return ::operator new(size); // allocate memory // or throw } int main() { Widget::set_new_handler(my_new_handler); Widget* w1 = new Widget; // if memory allocation fails, call my_new_handler string *ps = new string; // if memory allocation fails, call the global new_handler Widget::set_new_handler(0); Widget *pw2 = new Widget; // if memory allocation fails, throw an exception immediately. (There is no new_handler for Widget) }
这里,Widget类的operator new将会做下面的事情:
- 调用标准set_new_handler,参数为Widget的错误处理函数,这就将Widget的new-handler安装成为了全局的new-handler。
- 调用全局的operator new来执行实际的内存分配。如果分配失败,全局的operator new会触发Widget的new-handler,因为这个函数已经被安装为全局new-handler。如果全局的operator new最终不能分配内存,它会抛出bad_alloc异常。在这种情况下,Widget的operator new必须恢复原来的全局new-handler,然后传播异常。为了确保源new-handler总是能被恢复,Widget将全局new-handler作为资源来处理,使用资源管理对象来防止资源泄漏。
- 如果全局operator new能够为Widget对象分配足够的内存。Widget的operator new就会返回指向被分配内存的指针。管理全局new-handler的对象的析构函数会自动恢复调用Widget的operator new之前的new-handler。
placement new
placement new()是operator new()的一个重载版本,如果想在已经分配的内存中创建一个对象,使用new是不行的。
placement new允许在一个已经分配好的内存中(栈或堆中)构造一个新的对象。
表达式:
new(place_address) T new(place_address) T(initializer_list)
其中,
- place_address 参数必须是一个指针,指向一块预分配好的内存;
- initializer_list 提供了初始化列表(可能是空的),以便在构造新分配的对象中使用。
看个例子:
struct foo { foo(int x) {m = x; printf("foo(%d)\n", m);} ~foo() {printf("~foo()\n");} private: int m; }; int main() { void* addr = malloc(sizeof(foo)); foo* p = new(addr) foo(10); p->~foo(); free(p); return 0; }
可见,placement new不能使用delete(因为没有申请内存,placement new()没有与之对应的 placement delete())。因此,析构函数需要手动调用。
也可以使用栈内存
struct foo { foo(int x) {m = x; printf("foo(%d)\n", m);} ~foo() {printf("~foo()\n");} int get() {printf("m=%d\n", m);} private: int m; }; int main() { char mem[1024]; foo* p = new(mem) foo(10); p->get(); p->~foo(); return 0; }
总结下区别:
- new operator 既分配内存,又构建对象(通过构造函数),与之相反的是delete operator;
- operator new(),只管分配内存,不构建对象(不会调用构造函数),与之相反的是operator delete();
- placement new(),不管内存分配,只管调用构造函数构建对象,此时需要手动管理内存,并单独析构;