文章部分内容来自侯捷老师的C++内存管理视频。
0. placement new
即 原地构造 。 用法:
int *p = new int(10);
new(p) int(1000);
因为是在一个已有的空间上构造对象,所以,空间的释放,就由free 或者delete 负责了。
0.1 重载placement new
与后边的 3 :operator new 的重载无异,就是可以多增加几个函数,但是,第一个参数必须是std::size_t 类型,这个参数接收的是 new 后边类型的大小,不用显式传参 ,默认会传递。 重载示例:
#include <iostream>
using std::cout, std::endl;
class base{
public:
base(){
cout << "ctor" << endl;
}
~base(){
cout << "dtor" << endl;
}
void * operator new(std::size_t size, void *p){
cout << "std placement new" << endl;
return p;
}
void * operator new(std::size_t size, int a){
cout << "a = " << a << endl;
return malloc(size);
}
void * operator new(std::size_t size){
cout << "new diy" << endl;
return malloc(size);
}
void operator delete(void *p){
cout << "delete diy" << endl;
free(p);
}
private:
int a, b, c;
};
int main(){
base p;
new(&p) base;
new(4) base;
return 0;
}
运行结果:
ctor // 对象默认的构造函数输出的
std placement new // 默认placement new 输出的
ctor // 默认placement new 输出的
a = 4 // 重载的placement new 输出的
ctor // 重载的placement new 输出的
dtor // 析构
1. new operator
一般情况下,我们使用的new 都是new operator 。 用法:
int *p = new int(3);
其实底层有两个过程: 第一个过程:开辟空间 第二个过程:构造对象
2. delete operator
delete operator 就是平时常用的delete ,与new 搭配成对使用。 用法:
int *p = new int;
delete p;
3. operator new
是全局的库函数。
用法:
void* operator new(size_t size);
其实,operator new 底层调用的是malloc (调用形式也挺像的,传入一个开辟的Byte 数,返回一个void * )。
- 注意,
operator new 支持重载!当然了,如果重载了,那就要重载对应的operator delete
4. operator delete
是全局的库函数,与operator new 搭配使用。
用法:
int *p = new int;
operator delete(p);
底层调用的是free 。
5. new 做了哪些事情?
new 操作可以大致的认为包含了两个过程: 第一个过程:开辟相应的空间(使用operator new )。 第二个过程:在刚开辟的空间内构造一个对象(placement new )。
模拟new 操作。
int *p = static_cast<int*>( operator new(sizeof(int)) );
new(p) int(100);
上边说到operator new 和operator delete 支持重载,重载可以为我们自己的类类型定制化的分配内存。 只需要在类内重载operator new 和operator delete 。
编译器遇到new和delete时,首先看对应的类类型中是否有重载的operator new 和operator delete ,如果有,则优先调用重载的版本去分配内存,然后再原地构造。如果没有重载版本,就调用全局的库函数。
如果一个类内有重载的operator new 和operator delete ,会优先调用重载版本,代码如下:
#include <iostream>
using std::cout, std::endl;
class base{
public:
base(){
cout << "ctor" << endl;
}
~base(){
cout << "dtor" << endl;
}
void * operator new(std::size_t size){
cout << "new diy" << endl;
void *resPtr = malloc(size);
return resPtr;
}
void operator delete(void *p){
cout << "delete diy" << endl;
free(p);
}
private:
int a, b, c;
};
int main(){
base *p = new base;
delete p;
return 0;
}
运行结果:
new diy
ctor
dtor
delete diy
也可以显式的使用全局的库函数来new一个对象出来,把main函数中的new和delete换成全局的库函数,如下:
base *p = ::new base;
::delete p;
运行结果:
ctor
dtor
显式的调用了全局的库函数,而没有调用类类型重载的版本。
当然,如果不在类内进行重载,在全局进行重载,也是可以的。
这个两个函数,在类内重载,必须是静态的,即有static 修饰,因为调用这两个方法时,对象还没创建(调用new时)或者已经析构(调用delete时),this指针 此时是无效的,再想去通过this 调用一个方法,这是不可能的,所以必须是静态的。
上边的代码中,没有加static 修饰,也可以完成操作,是因为编译器替我们加上了static 。
6. array new 与 array delete
int *p = new int[10];
delete[] p;
array new 即是用new 申请一个数组空间,此时 p 指向第一个元素的起始位置,释放时必须使用对应的array delete ,也就是delete[ ] 。
对于基本数据类型来说,如果把上边的delete[] p 改成 delete p ,没什么问题,不会造成内存泄漏。但如果是自定义的类,并且类中有在堆上开辟内存空间的话,就会造成内存泄漏。 因为delete p 只会调用一个元素的析构函数,然后释放对应的空间,数组中的其余元素,虽然元素(类对象)本身的空间释放了,但是该对象原本来堆上开辟的内存空间,并没有释放掉(因为他的析构函数没有被调用),由此造成内存泄漏。
与上边的new 和 delete一样,array版本的new和delete也支持重载。 示例如下:
#include <iostream>
using std::cout, std::endl;
class base{
public:
base(){
cout << "ctor" << endl;
}
~base(){
cout << "dtor" << endl;
}
void * operator new[](std::size_t size){
cout << "new[] diy" << endl;
void *resPtr = malloc(size);
return resPtr;
}
void operator delete[](void *p){
cout << "delete[] diy" << endl;
free(p);
}
private:
int a, b, c;
};
int main(){
base *p = new base[2];
delete[] p;
return 0;
}
运行结果:
new[] diy
ctor
ctor
dtor
dtor
delete[] diy
7. delete[ ] 为什么能够释放数组?
在使用new 创建一个数组时,所开辟的实际内存大小比要申请的大小要大一些,在申请到的数组空间的上下都有额外的开销,来记录这块数组空间的相关信息,其中就包括数组空间的长度,每个元素的长度。所以 delete[ ] 可以顺利的知道数组的长度,并且可以成功的调用所有元素的析构函数。
|