前言
感谢大佬:https://www.cnblogs.com/luxiaoxun/archive/2012/08/10/2631812.html
因为这段时间在重新再次学习STL,在学习到deque时,遇到了allocator类,学习过程中又遇到operator new,发现现在我认识的new,已经不是我之间认识的那个new了,故我要好好认清她。
之前从C转向C++,因此免不了的就是malloc / free 和 new / delete对比。
1. malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
2. 对于内部数据类型(int, char, double...)的对象而言,光用malloc / free已经可以满足动态对象的要求。而在非内部数据类型(即类)对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数,这时候malloc / free已经无法满足,这时候new / delete应允而生。
而在面试中也会经常被问到,但是现在来看我了解还是皮毛。。。
如果读到这的话,写的可能比较冗余 请耐心看下去 谢谢!!!
new operator(delete operator) & operator new (operator delete)
首先我们得弄清楚 new operator(delete operator) & operator new (operator delete)它们不一样,不一样。。。
new operator (delete operator) 就是new (delete)操作符,而operator new (operator delete)是函数。
new operator
(1)调用operator new分配足够的空间,并调用相关对象的构造函数
(2)不可以被重载
operator new
(1)只分配所要求的空间,不调用相关对象的构造函数。当无法满足所要求分配的空间时,则
->如果有new_handler,则调用new_handler,否则
->如果没要求不抛出异常(以nothrow参数表达),则执行bad_alloc异常,否则
->返回null pointer
(2)可以被重载
(3)重载时,返回类型必须声明为void*
(4)重载时,第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t
(5)重载时,可以带其它参数
operator new 函数
请先注意operator new的version (1)、(2),对于version(3)后一节会单独拎出
函数原型:
1. void* operator new(std::size_t size);
//分配size字节存储空间,返回指向新分配的存储空间的指针,失败抛出一个bad_alloc异常
2.void* operator new(std::size_t size, const std::nothrow_t& nothrow_value);
//与第一个相同,只是在内存分配失败时,不会抛出异常,而是返回一个null指针
对于nothrow的相关东西,请移步于我的另一篇博文
http://www.cnblogs.com/SimonKly/p/7826660.html
3.void* operator new(std::size_t size, void* ptr) throw(); //placement new
//简单返回ptr,不会分配空间
这个版本的operator new重载方式,不同于前两个版本,而是单独拿出来的,被叫做placement new。是最难理解(我比较笨,琢磨了好久)
默认的分配和释放函数是标准库中特殊组件,有以下性质:
* 全局性: operator new三个版本都在全局命名空间声明
* 隐式声明: 无论是否包含头<new>,分配版本((1)和(2))在C++程序的每个翻译单元中隐式声明。
* 可重载: 分配版本((1)和(2))也是可替换的:程序可以提供自己的定义来替换默认提供的定义来产生上述结果,或者可以为特定类型重载。
下面有一个 http://www.cplusplus.com 例子,虽然很简单,但是对于理解我认为有很好的帮助
1 #include <iostream>
2 #include <new>
3
4 using namespace std;
5
6 class Simon
7 {
8 public:
9 Simon()
10 {
11 cout << "constructed [ " << this << " ]" << endl;
12 }
13
14 private:
15 int s;
16 };
17
18 int main(int argc, char** argv)
19 {
20 cout << "1: ";
21 Simon* s1 = new Simon;
22 // 分配内存调用 operator new(sizeof(Simon));
23 //然后构建对象 调用构造函数
24
25 cout << "2: ";
26 Simon* s2 = new(std::nothrow) Simon;
27 // 分配内存调用 operator new(sizeof(Simon), std::nothrow);
28 // 然后构建对象 调用构造函数
29
30 cout << "3: ";
31 new(s2) Simon;
32 // 不分配内存调用 operator new(sizeof(Simon), s2);
33 // 在s2的地址空间构建对象(可以从输出结果看出),调用构造函数
34
35 cout << "4: ";
36 Simon* s3 = (Simon*)operator new(sizeof(Simon));
37 // 只分配内存
38 // 不构建对象
39
40 delete s1;
41 delete s2;
42 delete s3;
43
44 cout << endl;
45 system("pause");
46 return 0;
47 }
运行结果:
总结:可以看出version (1)、(2)只是分配存储空间,而不构建对象 ,new operator分配空间时,调用version(1)or (2)来进行自定义化内存分配
Notice:
operator new可以显式调用为常规函数,但在C++中,new是具有非常特定行为的运算符:具有new运算符的表达式首先调用具有其类型说明符大小的函数operator new() 作为第一个参数,如果这是成功的,它会自动初始化或构造对象(如果需要的话)。
为什么有必要写自己的operator new和operator delete?why?(其实我也不知道,,,)
答案通常是:为了效率。缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也使得在某些特定的场合下,可以进一步改善它的性能。尤其在那些需要动态分配大量的但很小的对象的应用程序里,情况更是如此。具体可参考《Effective C++》中的第二章内存管理。
placement new
placement new其实就是operator new重载第三个版本,也不知道谁为什么叫其placement new,害的我还以为是一个新东西,之后发现就是 http://www.cplusplus.com (见下图)上,曰其为 placement 版本。。。
void* operator new(size_t size, void* p) throw()
{
return p;
}
placement new的执行忽视size_t参数,只返回还第二个参数。允许用户把一个对象放到一个指定的内存缓冲器中,参数p它就指向一个内存缓冲器。
placement new使用步骤
placement new主要适用于:
在对时间要求非常高的应用程序中,因为这些程序分配的时间是确定的;长时间运行而不被打断的程序,以及执行一个垃圾收集器(GC, garbage collector)
在很多情况下,placement new的使用方法和其他普通的new有所不同。这里提供了它的使用步骤。
第一步 缓存提前分配
有三种方式:
1.为了保证通过placement new使用的缓存区的memory alignment(内存队列)正确准备,使用普通的new来分配它:在堆上进行分配
class Task ;
char * buff = new [sizeof(Task)]; //分配内存
(请注意auto或者static内存并非都正确地为每一个对象类型排列,所以,你将不能以placement new使用它们。)
2.在栈上进行分配
class Task ;
char buf[N*sizeof(Task)]; //分配内存
3.还有一种方式,就是直接通过地址来使用。(必须是有意义的地址)
void* buf = reinterpret_cast<void*> (0xF00F);
第二步:对象的分配
在刚才已分配的缓存区调用placement new来构造一个对象。
Task *ptask = new (buf) Task
第三步:使用
按照普通方式使用分配的对象:
ptask->memberfunction();
ptask-> member;
//...
第四步:对象的析构
一旦你使用完这个对象,你必须调用它的析构函数来销毁它。按照下面的方式调用析构函数:
ptask->~Task(); //调用外在的析构函数
这里是不是就像GC机制呢?
你可以反复利用缓存并给它分配一个新的对象(重复步骤2,3,4),节省了申请空间的时间开销。
第五步:释放
如果你不打算再次使用这个缓存,你可以象这样释放它:delete [] buf;
跳过任何步骤就可能导致运行时间的崩溃,内存泄露,以及其它的意想不到的情况。如果你确实需要使用placement new,请认真遵循以上的步骤。
如下有一个简单程序:
#include <iostream> using namespace std; class animal { public: #if 1 //用于演示,无默认构造函数 animal() : num(0) { cout << "animal constructor default" << endl; } #endif animal(int _num) : num(_num) { cout << "animal constructor param" << endl; } ~animal() { cout << "animal destructor" << endl; } void show() { cout << this->num << endl; } void * operator new(size_t size, void *p) { return p; } private: int num; }; int main(int args, char ** argv) { // 一个动态animal数组 void *p = operator new(5 * sizeof(animal)); // 申请缓冲器 animal *a = static_cast<animal *>(p); // 转换类型 // 2.对象构建 for (int i = 0; i < 4; i++) { new(a + i) animal(i);// 调用重载构造 } new(a + 4) animal; // 也可以调用默认构造 // 3.使用 for (int i = 0; i < 5; i++) { (a + i)->show(); } // 4.销毁对象 for (int i = 0; i < 5; i++) { (a + i)->~animal(); } // 5.回收空间 delete[]p; cin.get(); return 0; }
运行结果:
placement new 存在的理由
1.用placement new 解决buffer的问题
问题描述:用new分配的数组缓冲时,由于调用了默认构造函数,因此执行效率上不佳。若没有默认构造函数则会发生编译时错误。如果你想在预分配的内存上创建对象,用缺省的new操作符是行不通的。要解决这个问题,你可以用placement new构造。它允许你构造一个新对象到预分配的内存上。
2.增大时空效率的问题
使用new操作符分配内存需要在堆中查找足够大的剩余空间,显然这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。placement new就可以解决这个问题。我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;而且不会出现在程序运行中途出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。
new 、operator new 和 placement new 区别
(1)new :不能被重载,其行为总是一致的。它先调用operator new分配内存,然后调用构造函数初始化那段内存。
new 操作符的执行过程:
1. 调用operator new分配内存 ;
2. 调用构造函数生成类对象;
3. 返回相应指针。
(2)operator new:只分配内存空间,要实现不同的内存分配行为,应该重载operator new,而不是new。
operator new就像operator + 一样,是可以重载的。如果类中没有重载operator new,那么调用的就是全局的::operator new来完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以重载的。
(3)placement new:只是operator new重载的一个版本。它并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此不能删除它,但需要调用对象的析构函数。
如果你想在已经分配的内存中创建一个对象,使用new时行不通的。也就是说placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void* p实际上就是指向一个已经分配好的内存缓冲区的的首地址