1.new delete总结
分配 | 释放 | 类别 | 可否重载 |
malloc() | free() | C函数 | 不可 |
new | delete | C++表达式 | 不可 |
::operator new() | ::operator delete() | C++函数 | 可 |
allocator<T>::allocate() | allocator<T>::deallocate() | C++ 标准库 | 可自由设计并搭配任何容器 |
new就是new operator,new[]就是 array new,new()就是placement new,::operator new()就是operator new函数,这是一个函数,下面一一详细介绍一下。
2.new expression(new operator)
Complex*pc = new Complex(1,2);
上述的new我们称为new表达式,也就是关键字new,有的书上称为new operator,Complex是自己定义的一个类一个复数罢了,new的动作为先申请一块内存,然后调用构造函数。源代码抽象如下:
Complex *pc;
try{
void* mem = operator new(sizeof(Complex));//allocate
pc = static_cast<Complex*>(mem);//cast 指针类型的转型
pc->Complex::Complex(1,2);//construct 注意只有编译器可以这样调用ctor(构造函数)
}
catch(std::bad_alloc){
//若allocation失败就不执行constructor
}
可以看到我们外部并不能直接通过指针去直接调用构造函数,但可以通过placement new来调用构造函数,形式为new(p)Complex(1,2),p为一个Comlex指针。
3.operator new函数
void *operator new(size_t size, const std::nothrow_t&)
_THROW0()
{
void *p;
while((p==malloc(size))==0){
_TRY_BEGIN
if(_callnewh(size)==0) break;
_CATCH(std::bad_alloc) return(0);
_CATCH_END
}
return (p);
}
以上代码是operator new 函数的源代码,该函数时可以被重载的,该函数输入一个size,malloc申请一个size的空间并返回,如果申请失败,会调用你可以自己定义的一个_callnewh(size)的一个函数,可以自己编辑怎么样删除多余的其他内存的函数,然后好分配给你正在需要申请的内存。std::nothrow_t表示这个函数不会抛出异常
4.delete expression
delete做的事情,先调用析构函数,然后把内存释放掉,调用接口如下:
Complex* pc = new Complex(1,2);
...
delete pc;
编译器编译后如下:
pc->~Complex();//先析构
operator delete(pc); //然后释放内存
前面说我们不能直接通过指针来调用构造函数,但是可以直接调用析构函数,operator delete的源码如下,可看出是调用的free函数。
void __cdecl operator delete(void *p) _THROW0(){
free(p);
}
5.array new(new[]), array delete(delete[])
很容易理解array new就是一次申请多个对象,并调用多次构造函数,而之前的new只调用了一次构造函数,array delete就是先进行多次析构函数,之前的delete只调用了一次析构函数,在delete[]进行多次析构函数时,有的是倒着析构的,即最后一个对象开始析构,有的是正着析构的。如果该调用delete[]的地方调用了delete,一般对于有指针的对象是有影响的,一般对于没有指针的类是没有影响的。
下面进行一个测试对比来看看这个过程。
#include<iostream>
using namespace std;
class A {
public:
int id;
A() :id(0) { cout << "default ctor. this=" << this << " id=" << id << endl; }
A(int i) :id(i) { cout << "ctor.this=" << this << " id=" << id << endl; }
~A() { cout << "dtor.this=" << this << " id=" << id << endl; }
};
int main() {
int size = 3;
A* buf = new A[size]; //必须要有默认构造函数
A* tmp = buf;
cout << "buf=" << buf << " tmp=" << tmp << endl;
for (int i = 0; i < size; i++)
new(tmp++)A(i); //placement new,通过指针直接调用构造函数
cout << "buf=" << buf << " tmp=" << tmp << endl;
delete[] buf;
while (1);
}
输出结果如下:
在上述代码中,调用delete[]和调用delete的效果是一样的,因为析构函数的作用其实不大,不会发生内存泄漏,下面是使用delete的实验结果,你用VS跑可能会报异常,不用管,如果你用内置类型就不会出现这样的问题了。
下面我们进行一个新的测试那就是在类的里面定义指针,看delete[]和delete的结果有何区别:
class A {
public:
string* id;
int capacity;
A(int cap = 5) {
capacity = cap;
id = new string[capacity];
}
~A() {
delete id;
}
};
int main(){
A* p = new A[3];
delete[] A;
}
这里使用的是delete会导致报错,因为string不是内置类型,在delete的时候析构函数是有意义的,而只有delete id的话,也就只调用了一次析构函数,因此是错误的。在调用delete的时候如果要释放数组,不是内置类型的需要使用delete[],内置类型用delete和delete[]都可以,因为在malloc的时候记录了总共要删除多大的内存,自定义类有指针就不能直接使用delete释放数组,没有指针一般用delete或者delete[]都可以。因为如果有指针申请了一块新的内存那么析构函数就很重要,申请多少个对象就要执行多少次析构函数。对比如下:
int* p = new int[10];
delete p;//和delete[] p相同的
string* p = new string[10];
delete[] p;//不可以使用delete p因为string 不是内置类型,构造函数有意义,必须执行构造函数
那么会有同学问我自己写了一个类,里面成员都是内置类型那么我可以delete和delete[]随便用吗,答案是可以的,一般只要成员没有指针都没有关系。
6.placement new
placement new允许我们将对象构造于已经分配好的内存中,没有placement delete,因为placement new 根本没有分配内存。placement new的使用方式如下,可看到是先分配了一块内存,然后调用指针在已经分配的内存上构造了一个Complex对象。
#include<new>
char *buf = new char[sizeof(Complex)*3];
Complex *pc = new(buf)Complex(1,2);
....
delete[] buf;
下面我们看抽象后的placement new源码:
Complex *pc;
try{
void* mem = operator new(sizeof(Complex),buf);
pc = static_cast<Complex*>(mem);
pc->Complex::Complex(1,2);
}
catch(std::bad_alloc){
}
该operator new 函数的源码如下:
void* operator new(size_t, void* loc){
return loc;//因为不需要分配内存,因此只需要把这个定点的指针返回即可
}