说了new产了3个步骤;
- 分配内存
- 调用构造函数
- 把地址返回
2 和 3 由编译器来完成;
有的小伙伴不太懂,说明一下;
先给结论:
使用关键字(new,delete)和直接调用全局函数(operator new / delete ) 不一样的 , 编译器会做额外的事
new关键字调用 先::operator new 分配空间 - > 调用构造/返回
delete关键字调用::operator delete ,先调用析构 -> 释放空间
如果直接调用全局函数::operator new / delete ,则需要自己调用构造/析构;
如同placement new(定位new ,名字有点怪,不分配空间) 需要自己调用构造/析构;
如何证明 new 关键字 由编译器来完成?
首先 new 关键字将调用 , 以下3个版本的其中一个 ;
// 一般的 new SomeClass() , 将调用此版本;
void* operator new (std::size_t size) throw (std::bad_alloc);
//没有异常的版本 , new (nothrow) SomeClass();
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
/*
定位new , 一般用于内存池 , stl中很多这样的
比如:
char * p = (char*)malloc(100); 申请内存
new (p) SomeClass(); //调用构造函数
*/
void* operator new (std::size_t size, void* ptr) throw();
下面例子中重写了成员的delete,为方便起见,先说明一下:
//全局作用域的delete
void operator delete(void * p) throw()
{
.....
}
//一般的class专属的delete , 有额外一个size参数
//为什么会有size , 因为继承的关系,每个class 大小不一样
void operator delete(void * p, size_t size) throw()
{
....
}
一个例子说明问题:
class A
{
public:
A(){
printf("A() \n");
}
//也可用于全局作用域 (意思是 可以把这个函数剪切到全局)
static void * operator new (size_t size)
{
void * p = malloc(size);
printf("new addr:%p , size:%ld\n", p,size);
return p;
}
//也可用于全局作用域
static void operator delete(void * p)
{
puts("operator delete(void * p)");
free(p);
}
//class专属的delete , 如果没有上面的delete就调用这个,
//为什么会有size , 因为继承的关系,每个class 大小不一样
static void operator delete(void * p, size_t size)
{
printf("operator delete(void * p, size_t size) . %d\n", size);
free(p);
}
~A(){ printf("~A()\n"); }
};
int _tmain(int argc, _TCHAR* argv[])
{
A * 直接使用 = (A*)::operator new(sizeof(A)); // 只会分配一个地址,不会调用构造函数
// A * 使用new关键字 = new A(); 由编译器先调用operator new, 再调用构造,以及返回
return 0;
}
因此new关键字由编译器完成额外的工作;
创建对象时(调用构造函数),operator new通过new关键字隐式调用 ,前后加了一些额外的代码;
如果想又::operator new , 还能正常调用构造怎么办?
下面贴出main中的代码,其他不变:
A * a = (A*)::operator new(sizeof(A)); //用全局的
/*
用的placement new 直接返回地址;
void* operator new (std::size_t size, void* ptr) throw()
{ return ptr;}
下面2种方式来调用构造
*/
::new (a) A(); //通过定位new, 来构造;
a->A::A(); //也可以这样,再此调用构造
a->~A(); //调用析构, 如果不调用,则不会析构
::operator delete(a); //用全局的,没有调用类的delete
从上面代码可以看出 , new 关键子由编译器帮忙,增加了额外操作;
同理 ,不仅仅是 new 关键字, 还有delete 关键字 ,如果直接使用::operator delete 仅仅是释放,不会调用析构.
对于placement new 一般用于内存池 或者 需要频繁的new obj, delete obj;那么可以先malloc一块内存,然后在上面
构造析构;
同时如果重载了placement new , 则必须有一个与之参数匹配的 operator delete, 比如:
//重载了 placement new
void * operator new (size_t size, int a, int b)
{
printf(" operator new ( size_t size , int %d, int %d) \n ", a, b);
return malloc(size);
}
//必须也重载一个与上面参数类型一致的 operator delete
// 这个函数仅仅在 new 失败的时候会被自动调用
//如果不写这个函数,在vs下有警告
void operator delete(void * p, int, int) throw()
{
printf("operator new (size_t size, int a, int b) failed\n");
free(p);
}
int _tmain(int argc, _TCHAR* argv[])
{
//A 还是之前那个
A * a = new(1,2) A(); //使用重载的
// error C2541: 'delete' : cannot delete objects that are not pointers
// delete(1,2) a; 只有当new(1,2) A() 抛出异常时被调用,你不能直接使用
//此delete 并非上面重载的那个,重载那个仅在 new (1,2) A()失败时被自动调用
//此delete 还是与往常一样, 先析构 后释放
delete a;
return 0;
}
至于new[] 在32位情况下会额外多分配4个字节给你,用于记录长度:
class A
{
public:
A(){printf("A() \n");}
void * operator new[](size_t size){
void * p = malloc(size);
printf("operator new[](size_t %ld) , %p\n", size , p);
return p;
}
void operator delete[](void * p, size_t size)
{
printf("operator delete[](void * %p, size_t %ld)\n", p, size);
free(p);
}
~A(){ printf("~A()\n"); }
};
int _tmain(int argc, _TCHAR* argv[])
{
A * a = new A[3]; //编译器将调用A::operator new[] , 同时执行构造
printf("a = %p\n", a);
int * ptr = (int*)a;
printf("数组长度 : %d\n", *(--ptr));//往前偏移4个字节
delete[] a; //编译器将调用A::operator delete[] 前先析构
return 0;
}
输出:
operator new[](size_t 7) , 004E90C8
A()
A()
A()
a = 004E90CC
长度 : 3
~A()
~A()
~A()
operator delete[](void * 004E90C8, size_t 1)