深入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);

  }

};

 
posted @ 2011-06-12 22:52  IT小小鸟bird  阅读(704)  评论(1)    收藏  举报