new表达式,operator new和placement new介绍

new/delete是c++中动态构造对象的表达式 ,一般情况下的new/delete都是指的new/delete表达式,这是一个操作符,和sizeof一样,不能改变其意义。

new/delete表达式的声明如下:

::(optional) new (placement_params)(optional) ( type )initializer(optional)
::(optional) delete  expression  

除了全局作用符::和初始化参数,还有个 placement_params,这是不常见的,要理解这个参数的作用,就要了解operator new和placement new。

众所周知,new表达式做了两个工作:1.分配内存;2.在分配的内存上调用构造函数构造对象。比如我们分配一个string对象

string *str = new string(“Kian”);

编译器首先调用operator new分配一块内存,类似于malloc,然后在mem上面调用构造函数,

1.void *mem = operator new(sizeof(string));

2. create string at men.

第二步我们是控制不了的,但是operator new却是可以修改的。

Operator new/delete的声明如下:

void* operator new  ( std::size_t count );
void* operator new  ( std::size_t count, const std::nothrow_t& tag);
void operator delete  ( void* ptr );
void operator delete  ( void* ptr, const std::nothrow_t& tag);

第二种带参数tag的声明称为nothrow形式,因为现在的operator new如果分配内存失败的话会抛出bad_alloc异常. 有时候我们不想抛出异常,而是根据返回值判断内存分配失败与否,nothrow形式就是这个作用,失败时不抛出异常,而是返回null指针。

我们可以直接重载operator new, 定制自己的内存分配策略,常见的作用是优化内存使用性能。重载operator new不需要看见声明就可以直接使用。我们重定义一个简单的版本:

void* operator new(std::size_t size){
  printf("operator new called, size=%d
", size);
  return malloc(size);
}
 
void operator delete(void *ptr){
  printf("operator delete called
");
  free(ptr);
}

int main(){
  int *i = new int(2);
  delete i;
  std::string *str = new std::string("Kian");
  delete str;
}

运行结果:

operator new called, size=4

operator delete called

operator new called, size=4

operator new called, size=17

operator delete called

operator delete called

可是有时候我们希望拥有更多的功能,比如记录内存分配释放的位置,用于检测内存错误,或者直接在已有的内存上构造对象,那么必须定义更多参数,这就需要placement new/delete,声明如下.

void* operator new  ( std::size_t count, void* ptr );
void* operator new  ( std::size_t count, user-defined-args... );
void operator delete  ( void* ptr, void* place);
void operator delete  ( void* ptr, user-defined-args...); 

可以直接在已有内存上构造对象:

void *mem = (void*)malloc(sizeof(int));
int *j = new(mem) int(3);
printf("mem=%p, j=%p, *j = %d
",mem, j, *j);
free(mem);

运行结果:

mem=0x8a48008, j=0x8a48008, *j = 3

可以看出new直接在mem上面构造了对象。

目前,void* operator new  ( std::size_t count, void* ptr )在全局域还不能被重载,但是void* operator new  ( std::size_t count, user-defined-args... )可以自由定义。

比如记录内存分配发生的位置:

void* operator new(std::size_t size){
  printf("operator new called, size=%d
", size);
  return malloc(size);
}

void operator delete(void *ptr){
  printf("operator delete called
");
  free(ptr);
}

void* operator new(std::size_t size, char* filename, int line){
  printf("new called at %s:%d  size=%d 
", filename, line, size);
  return ::operator new(size);
}

void operator delete(void *place, char* filename, int line) {
  printf("delete called at %s:%d  place=%p 
", filename, line, place);
  ::operator delete(place);
}

int main(){
  int *k = new(__FILE__, __LINE__) int(1);
  printf("k=%p *k=%d 
", k, *k);
  delete k;
}

运行结果:

new called at testnew.cpp:41  size=4

operator new called, size=4

k=0x847f008 *k=1

operator delete called

在每个new中打印了文件名和行号,不过细心的你会发现delete时并没有调用重载的placement delete ,这个delete只有在构造对象时抛出了异常才会调用,我们写一个简单的class来看看:

class ThrowExcept {
 public:
  ThrowExcept(int v):value_(v){ throw 1;}
 private:
  int value_;
};

int main(){
  ThrowExcept *t;
  try{
  t = new(__FILE__, __LINE__) ThrowExcept(10);
  }catch(int &e){
    printf("catch exception %d
", e);
  }
}

运行结果:

new called at testnew.cpp:53  size=4

operator new called, size=4

delete called at testnew.cpp:53  place=0x9e2e008

operator delete called

catch exception 1

自定义的delete被正常调用,这么做的原因在于如果构造函数抛出异常,系统正常的operator delete并不知道用户自定义的placement new做了什么,自然也不知道怎么去释放。所以如果自己定义placement new, 一定要定义对应的palcement delete,不然可能出现memory leak。

 

http://en.cppreference.com/w/cpp/memory/new/operator_new

http://en.cppreference.com/w/cpp/language/new

《effective/more effective c++》

原文地址:https://www.cnblogs.com/coderkian/p/3688822.html