深入new和delete运算符
New 的 3 种形态: new operator 、 operator new 、 placement new
new 操作符 (new 表达式 , new operator , new expression): 通常我们调用 X * pX = new X 时使用的就是这个操作符 , 它由语言内建 , 不能重载 , 不能改变其行为 . 它包括分配内存的 operator new 和调用构造函数的 placement new . new 关键字实际上做了三件事:获得一块内存空间、调用构造函数、返回正确的指针。当然,如果我们创建的是简单类型的变量,那么第二步会被省略。
operator new : operator new 是一个函数 , void * operator new(size_t size) .它分配指定大小的内存 , 可以被重载 , 可以添加额外的参数 , 但第一个参数必须为 size_t . operator new 除了被 new operator 调用外也可以直接被调用 : void * rawMem = operator new(sizeof(X)) .这种用法和调用 malloc 一样。
placement new : placement new 在一块指定的内存上调用构造函数 , 包含头文件 之后也可以直接使用 placement new: X * pX = new (rawMem) X .
与 new operator 类似 , 对于 delete operator, 也存在 operator delete: void operator delete(void *), 析构方法 pX->~X()。
New 的基本使用指南:
想在堆上建立一个对象,应该用 new 操作符。它既分配内存又为对象调用构造函数。如果仅仅想分配内存,就应该调用 operator new 函数;它不会调用构造函数。如果想定制在堆对象被建立时的内存分配过程,你应该写你自己的 operator new 函数,然后使用 new 操作符, new 操作符会调用定制的 operator new .如果想在一块已经获得指针的内存里建立一个对象,应该用 placement new . placement new 主要适用于:在对时间要求非常高的应用程序中,因为这些程序分配的时间是确定的;长时间运行而不被打断的程序;以及执行一个垃圾收集器 (garbage collector) .
由于 placement new 的特殊性,下面给出其常规使用步骤:
class Task ; //
char * buff = new char[sizeof(Task)]; // 1) 分配内存
Task *ptask = new(buff) Task ; // 2) 构造对象
ptask->suspend(); // 3) 正常使用对象
ptask->resume();
ptask->~Task(); // 4) 析构对象
delete [] buff; // 5) 释放内存
显然, placement new 可以提高效率,但增加了程序的复杂度,需要自行管理对象的生存期。 Placement new 的使用大量存在于 STL 中。
深入new/delete:Operator new的全局重载
Operator new 的全局重载
我们经常看到这么一句话: operator new 可以重载, placement new 不可重载。其实此处所说的不可重载应该是指全局的 placement new 不可重载,对于类域中的 placement new 是可以重载的,而且只要重载了任何一种形式的 operator new 都应该顺便重载 placement new , 即 void * operator new(std::size_t count, void *ptr) .
操作符重载一般用于特定类型,名字解析过程同一般的函数重载。 Operator new 由于其特殊性,编译器提供了默认提供 6 种全局重载形式,同时还允许用户提供自定义的全局 operator new ,其参数甚至可以和全局版本一样,除全局 placement new 外。对于类域,任何形式的 new 都是可以重载的,包括 placement new 形式。
全局的 operator new( 函数 ) 有六种重载形式
void *operator new(std::size_t count)
throw(std::bad_alloc); // 一般的版本
void *operator new(std::size_t count, // 兼容早版本的 new
const std::nothrow_t&) throw(); // 内存分配失败不会抛出异常
void *operator new(std::size_t count, void *ptr) throw(); //placement 版本
void *operator new[](std::size_t count) //
throw(std::bad_alloc);
void *operator new[](std::size_t count, //
const std::nothrow_t&) throw();
void *operator new[](std::size_t count, void *ptr) throw();
重载 operator new 规则
重载 operator new 的参数个数是可以任意的 , 只需要保证第一个参数为 size_t, 返回类型为 void * 即可 , 而且其重载的参数类型也不必包含自定义类型 . 更一般的说 , operator new 的重载更像是一个函数的重载 , 而不是一个操作符的重载 . 如
全局重载示例:
|
void* operator new(size_t size) // 重载成功 { printf("global new“n"); return malloc(size); //return ::operator new(size); // 递归调用提示 (warning) } //void *operator new(std::size_t size, void *ptr) // 无法重载 //{ // printf("global new“n"); // return ::operator new(size,ptr); //} void * operator new(size_t size, const std::nothrow_t& e) // 重载成功 , 递归调用提示 (warning) { printf("global new“n"); return ::operator new(size, e); } |
一般形式的 operator new 重载示例:
|
void * operator new(size_t size, int x, int y, int z) { ... } X * pX = new (1, 2, 3) X; char data[1000][sizeof(foo)]; inline void* operator new(size_t size, int n) { return data[n]; } |
就可以使用这样有趣的语法来创建对象
foo *p=new(6) foo(); // 把对象创建在 data 的第六个单元上
深入new/delete:类域的operator new重载
类域的 operator new 重载
为 class 重载 operator new 时必须定义为类的静态函数 ( 默认为 static 函数 ) .重载 operator new 更多的是为了提高程序效率,比如使用静态内存代替动态分配,启用小对象分配器等。但是要正确重载类域的 operator new 并不容易,有很多规则需要注意: 1) 总是成对提供 new/delete ; 2) 如重载 operator new 一定要同时提供标准形式的 new .
举例:
|
class T { static void* operator new(std::size_t); static void* operator new(std::size_t, CustomAllocator&); static void operator delete(void*, std::size_t); }; T* p = new(alloc) T; 上述代码将被编译器展成下述代码 ( 猜测,如果有高人知道如何查看编译器生成的中间代码,烦请告知。 ) // compiler-generated code for T* p = new(alloc) T; // void* __compilerTemp = T::operator new (sizeof(T), alloc); // 1 T* p; try { p = new (__compilerTemp) T; // 2 // construct a T at address __compilerTemp } catch(...) { // constructor failed, attention here… T::operator delete (__compilerTemp, sizeof(T), alloc); // 3 throw; } |
注释 1 : operator new 用于分配内存, new 的第一步骤;
注释 2 : placement new 用于在于地址上构建对象,也就是调用 T 的构造函数。
注释 3 :与 operator new 配套的 operator delete .
C++ 标准规定:当且仅当 operator delete 的重载存在时,以上代码才能生成。否则,在构造函数失败的情况下,代码不会调用 operator delete .也就是说,如果构造函数抛异常,内存将泄漏。
这意味着:当提供 void* operator new(parms) 重载时,必须同时提供 void operator delete(void*, parms), 这里参数列表的第一个参数总为 std::size_t . 对于 operator new[] 也是如此。例外,对于 placement new 由于并不实际分配内存,可不重载相应的 operator delete .
避免隐藏全局的 operator new 和基类重定义的 operator new
前面已经提到过 C++ 名字查找规则,对于重载的 operator new 也需要遵循此规则。当类中重载 operator new ,那么全局的 operator new 将被隐藏;如果派生类重载 operator new ,那么基类的版本将被隐藏。由于 placement new 大量用于 STL 的优化中,故一定要避免隐藏 placement new .隐藏 operator new 的这类错误编译器会给出提示。
重载 opeartor new 并避免隐藏全局 operator new 的途径
1) 对于基类重载的 operator new ,使用 using 来导入相应名字。
|
class C : public B {// … public: static void* operator new(size_t, MemoryPool&); // private version using B::operator new; }; |
2) 对一般重载 operator new 的类,除了提供私有版本外,应该对所有全局 operator new 形式提供转换函数。如下:
|
class C {// … public: static void* operator new(size_t, MemoryPool&); // private version static void* operator new(std::size_t s) { return ::operator new(s); // global version } static void* operator new(std::size_t s, std::nothrow_t nt) throw() { return ::operator new(s, nt); } static void* operator new(std::size_t s, void* p) { return ::operator new(s, p); } }; |

浙公网安备 33010602011771号