文章目录
文章部分内容来自侯捷老师的C++内存管理视频。
0. placement new
即 原地构造
。
用法:
int *p = new int(10); // new operator
new(p) int(1000); // 在 p 指向的内存上 构造一个int对象,即placement new
// 此时 *P 为 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;
}
// 标准库中的 placement new, 这里什么都没做,只是返回原来的指针。
void * operator new(std::size_t size, void *p){
cout << "std placement new" << endl;
return p;
}
//重载 placement new,附加了一个参数 a,参数size会默认传递,不用显式传参。
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 operator 开辟空间
new(p) int(100); // placement new 在p指向的内存上构造一个对象
上边说到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[ ]
可以顺利的知道数组的长度,并且可以成功的调用所有元素的析构函数。