8.了解不同意义的new与delete

前言

 
new操作符(new operator)与new操作(operator new)含义完全不同。


具体区别

 
考虑如下代码:

1
string *ps = new string("Memory Management");

这里使用的是new操作符(new operator),它就像sizeof一样是内置的,无法改变其功能与含义。它的功能有以下两个:

  1. 分配足够大的内存以便于容纳对象
  2. 调用构造函数初始化内存中的对象

我们能够改变的是如何为对象分配内存。new操作符使用一个函数来完成内存分配工作,此函数名为operator new.


operator new

 
operator new有声明如下:

1
void * operator new(size_t size);

返回值类型是void*,因为此函数返回了一个raw指针指向未初始化的内存。size代表了分配多少字节。该函数允许重载,但是第一个形参应当保证为size.
类似于malloc,opertaor new只负责分配内存,它对构造函数一无所知。我们可以认为,当执行如下语句时:
1
string *ps = new string("Memory Management");

其行为应当类似于:
1
2
3
void *memory = operator new(sizeof(string));
call string::string("Memory Management") on *memory;//初始化对象
string *ps = static_cast<string*>(memory);

值得注意的是第二部涉及构造函数调用,程序员无法显式在内存中调用构造,因此如果我们想在heap上构建对象,就必须要使用new操作符。


placement new(Effective C++ 53)

 
假定我们已经获取了一块raw内存,我们需要在这些内存中构造对象,我们可以使用一个特殊的operator new,它又被称为placement new,其构造实例如下:

1
2
3
4
5
6
7
8
class Widget {
public:
Widget(int widgetSize);
...
};
Widget * constructWidgetInBuffer(void *buffer,int widgetSize){
return new(buffer) Widget(widgetSize);
}

这个函数返回一个指针,指向一个Widget对象,对象在传递给函数的buffer里分配。
placement new的定义式大致如下所示:
1
2
3
void * operator new(size_t, void *location){
return location;
}


operator new与new operator使用总结

  • 如果你需要在堆上建立一个对象,使用new操作符:它既分配内存又为对象调用构造函数。
  • 如果你只需要分配内存,那你只要用operator new。
  • 如果你希望在内存分配时自定义操作,那无疑需要重载operator new。
  • 如果你需要在指定的raw 内存位置构建对象,那使用placement new。

Deletion and Memory Deallocation

 
为了避免内存泄漏,每一个动态内存分配必须与一个deallocation对应。delete operator 与operator delete的关系类似于new operator与operator new。
如果我们是用placement new在内存中建立对象,那我们应该避免在该内存中使用delete操作符。因为delete操作符调用operator delete,而后者并不知道该去哪里释放内存(Effecive C++ 53)。所以必须显式调用对象的析构函数来解除构造函数的影响:

1
2
3
4
5
6
7
8
9
//在共享内存中分配和释放内存的函数
void * mallocShared(size_t size);
void freeShared(void *memory);
void *sharedMemory = mallocShared(sizeof(Widget));
Widget *pw = constructWidgetInBuffer(sharedMemory, 10);
...
delete pw; //结果不确定! 共享内存来自mallocShared, 而不是 operator new
pw->~Widget();//正确析构
freeShared(pw); //释放内存

(此处内容类似于alloctor)


数组

 
如果我们不是一次建立一个对象,而是试图分配一个数组:

1
string *ps = new string[10];

此时调用的依然是new operator,不过较于之前有两点不同:

  1. 内存分配函数不再是operator new,而是operator new[],它也可以被重载。
  2. new operator调用了n次构造函数。

同理,delete也发生了改变。


总结

 
在试图定制new与delete的行为时,有准则如下:只能改变它们为完成功能所采用的方法,而不能更改它们可以完成什么功能。(You can modify how they do what they do, but what they do is fixed by the language.)