19.1.1 重载new和delete
string *sp = new string("a value");
string *arr = new string[10];
一条new
表达式,实际执行了三步操作
new
表达式调用一个名为operator new
(或operator new[]
)的标准库函数,该函数分配一块足够大的、原始的、未命名的内存空间以便存储特定类型的对象(或数组)- 编译器运行相应的构造函数以构造这些对象,并为其传入初始值
- 对象被分配了空间并构造完成,返回一个指向该对象的指针
delete sp;
delete[] arr;
一条delete
表达式,实际执行了两步操作
- 对
sp
所指的对象或arr
所指的数组中的元素执行相应的析构函数 - 编译器调用名为
operator delete
(或者operator delete[]
)的标准库函数释放内存空间
可以重载
operator new
和operator delete
函数,但是无法改变new
运算符和delete
运算符的基本含义
如果不使用自定义的new
和delete
,可以使用作用域运算符令new
和delete
表达式忽略定义在类中的函数,即::new
和::delete
operator new
函数的返回类型必须是void*
,第一个形参的类型必须是size_t
且该形参不能含有默认实参,调用时,把存储指定类型对象所需的字节数传给size_t
形参。如果是operator new[]
,则传递存储数组中所有元素所需的空间
operator delete
函数的返回类型必须是void
,第一个形参的类型必须是void*
operator new
和operator delete
函数需要调用malloc
和free
函数进行内存分配和释放
void* operator new(size_t size) {
if (void *mem = malloc(size))
return mem;
else
throw bad_alloc();
}
void operator delete(void *mem) noexcept { // 不能抛出异常
free(mem);
}
直接调用析构函数会销毁对象,但是不会释放内存
19.1.2 定位new表达式(placement new)
对于operator new
分配的内存空间,我们无法直接调用构造函数来构造对象,可以使用new
的定位new
形式构造对象,定位new
允许我们在一个特定的、预先分配的内存地址上构造对象
placement new
的形式如下
new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
place_address
必须是一个指针,同时在initializers
中提供一个以逗号分隔的初始值列表,该初始值将用于构造新分配的对象
比如下面这样
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; }
};
char *buf = new char[sizeof(A)]; // 先分配内存,相当于char[4],得到一个指针
A *pc = new(buf) A(1); // 用定位new调用构造函数
// ctor, this=0x5644905d2e70 id=1
placement new
内部的过程分成三步
A *pc;
void *mem = operator new(sizeof(A), buf); // 直接返回传入的指针
pc = static_cast<A*>(mem); // 指针类型转换为A*
pc->A::A(1); // 调用构造函数
里面第一步也是调用operator new
,但它不是分配内存,只是简单地返回指针参数,类似于下面这样
void* operator new(size_t size, void* loc) {
return loc; // 直接将传入的指针返回
}
应用的话,比如调用new[]
时只能调用默认构造函数,如果我们想用自定义的构造函数来构造对象,就可以使用placement new
来调用自定义构造函数
int main() {
A *buf = new A[3];
A *tmp = buf;
for (int i = 0; i < 3; i++) {
new(tmp++) A(i); // placement new调用构造函数
}
delete[] buf;
return 0;
}
default ctor, this=0x565188649e78 id=0
default ctor, this=0x565188649e7c id=0
default ctor, this=0x565188649e80 id=0
ctor, this=0x565188649e78 id=0
ctor, this=0x565188649e7c id=1
ctor, this=0x565188649e80 id=2
dtor, this=0x565188649e80 id=2
dtor, this=0x565188649e7c id=1
dtor, this=0x565188649e78 id=0 // 析构和构造的顺序是相反的
定位new
使得我们可以使用栈或静态存储区的内存空间
#include <iostream>
#include <new>
using namespace std;
const int N = 4;
int main() {
char buffer[512];
cout << (void*)buffer << endl;
double *pd = new(buffer)double[N];
for (int i = 0; i < N; i++) {
pd[i] = i;
}
for (int i = 0; i < N; i++) {
cout << pd[i] << ": " << &pd[i] << endl;
}
return 0;
}
0x7ffd8418fa90
0: 0x7ffd8418fa90
1: 0x7ffd8418fa98
2: 0x7ffd8418faa0
3: 0x7ffd8418faa8
这里的空间是在栈上分配的,只是使用定位new
对空间进行赋值