new的实现
Complex* pc = new Complex(1, 2);
//编译器转化为三步
void* mem = operator new(sizeof(Complex)); //分配内存
pc = static_cast<Complex*>(mem); //将void指针转换为所需类型的指针
pc->Complex::Complex(1, 2); //构造函数
- 第一步调用operator new,内部调用malloc(n),分配所需大小的内存。指针指向分配内存的起始位置。
- 第二部将void*指针转换为所需类型的指针
- 第三步在指针所指的内存中调用构造函数创建所需的对象
delete的实现
delete pc;
//编译器转化为两步
Complex::~Complex(pc); //析构函数
operator delete(pc); //释放内存
- 第一步对应析构函数
- 第二步调用operator delete,内部调用free(ps),释放内存
- 在上例中一共有两个删除操作,第一次是调用析构函数释放它动态分配的内存,第二次释放动态分配ps所指的内存
new[ ]与delete[ ]要搭配使用
- delete[ ]会表示要删除一个数组,要调用多次析构函数,最后再释放new[ ] 申请的空间,不会发生内存泄漏。
- delete表示要删除一个元素,只会调用一次析构函数,如果申请的数组中每个元素均有一个动态分配的空间,只调用一次析构函数会导致其余元素申请的空间发生内存泄漏,而不是申请数组的空间发生内存泄漏。
重载operator new、operator delete、operator new[ ]、operator delete[ ]
全局范围内的重载
void* myAlloc(size_t size)
{ return malloc(size); }
void myFree(void* ptr)
{ return free(ptr);}
//它们不可以被声明于一个namespace中
inline void* operator new(size_t size)
{ cout<< "global new() \n"; return myAlloc(size);}
inline void* operator new[](size_t size)
{ cout<< "global new[]() \n"; return myAlloc(size);}
inline void operator delete(void* ptr)
{ cout<< "global delete() \n"; myFree(ptr);}
inline void operator delete[](void* ptr)
{ cout<< "global delete[]()\n"; myFree(ptr);}
- operator new一定要传入一个参数表示大小,这个是由编译器传进来的
- operator delete一定要传入一个指针,表示要释放的地址
- 这四个函数的重载是全局函数,它的影响巨大
类内的重载
class Foo{
public:
void* operator new(size_t);
void operator delete(void*, size_t);//size_t是可选的参数,传入一个参数也可以
//...
};
- new分解为三个动作,delete分解为两个动作,这样可以用自定义的操作符接管内存的分配和释放行为
- 接管的动作可以做一个内存池
Foo* p = new Foo;
/*----------------------new分解为三步--------------------------
try{
void* mem = operator new(sizeof(Foo));
p = static_cast<Foo*>(mem);
p->Foo::Foo();
}
*/--------------------------------------------------------------
delete p;
/*----------------------delete分解为两步---------------------
p->~Foo();
operator delete(p);
*/-----------------------------------------------------------------
- 重载member operator new [ ] / delete[ ]
class Foo{
public:
void* operator new[](size_t);
void operator delete[](void*, size_t);//size_t是可选参数
//...
}
Foo* p = new Foo[N];
/*----------------------------new[]分解为三步---------------------
void* mem = operator new(sizeof(Foo)*N + 4);//vc中数组会用4个字节储存数组的大小
p = static_cast<Foo*>(mem);
p->Foo::Foo;//N次
*/----------------------------------------------------------------
...
delete[] p;
/*------------------------------delete[]分解为两步--------------------
p->~Foo();//N次
operator delete(p);
*/-----------------------------------------------------------------
- 无视类内的重载,调用全局的operator new
- new调用operator new[ ]时传入的大小,不但包含N*每个元素的大小,还包括一个计数器,记录数组一共有几个元素
- 构造函数是从头到尾构造每一个元素的,析构函数是从尾到头释放每一个元素
Foo* pf = ::new Foo;// void* ::operator new(size_t);
::delete pf;//void ::operator delete(void*)
- 重载class member operator new(),且可以重载很多个版本,前提是每一版本都有独一无二的参数列,其中第一参数必须是size_t,其余参数以new所指定的placement arguments为初值
- 出现于new(…)小括号内的便是所谓的placement arguments
Foo* pf = new(300, 'c')Foo;
-
也可以重载class member operator delete(),写出多个版本。但它们绝不会被delete调用
-
只有当调用operator new()后调用构造函数时抛出异常,即构造没有成功,才会调用这些重载版本的operator delete()
-
它们只可能这样被调用,主要用来完成未能完全创建成功的对象所占用的内存
-
即使operator delete(…)未能与operator new(…)一一对应,也不会有任何报错。因为operator delete(…)只会在抛出异常时被调用,编译器认为没有定义表示不在乎异常
-
标准库对placement new的示例
-
create中调用operator new(extra)表示分配一个Rep加上extra,为的是可以增加一个引用计数的功能,Rep表示计数器,即有多少一起共享该字符串
-
当想实现多分配一些东西时可以使用operator new(…)