在c++中new和delete操作是我们操作内存最常用的一对操作符,在使用new时编译器会申请内存,然后调用类的构造函数来初始化对象,调用delete会销毁对象同时释放该对象占用的内存,并且我们可以重载new操作符。
还有一个不常用的new操作placement new,在使用时需要我们传入一个指针,此时会在该指针指向的内存空间构造该对象,该指针指向的地址可以使堆空间,栈空间,也可以是静态存储区。
如果有这样一个场景,我们需要大量的申请一块类似的内存空间,然后又释放掉,比如在在一个server中对于客户端的请求,每个客户端的每一次上行数据我们都需要为此申请一块内存,当我们处理完请求给客户端下行回复是释放掉该内存,表面上看者符合c++的内存管理要求,没有什么错误,但是仔细想想很不合理,为什么我们每个请求都要重新申请一块内存呢,要知道每一次内从的申请,系统都要在内存中找到一块合适大小的连续的内存空间,这个过程是很慢的(相对而言),极端情况下,如果当前系统中有大量的内存碎片,并且我们申请的空间很大,甚至有可能失败。为什么我们不能共用一块我们事先准备好的内存呢?可以的,我们可以使用placement new来构造对象,那么就会在我们指定的内存空间中构造对象。
using namespace std;
static char macBuffer[1024 * 6] = {0};
class MyClass {
public:
MyClass(std::string b) {
this->a = b;
}
~MyClass() {
std::cout << "~MyClass" << std::endl;
}
std::string GetA() { return a;}
std::string a;
};
int main() {
MyClass *ptMyClass = NULL;
char *p = (char *) malloc(sizeof(MyClass) + sizeof(int));
ptMyClass = new(p) MyClass("Hello world");
std::cout << "&ptMyClass ==>" << ptMyClass << std::endl;
std::cout << "&p ==>" << (int*)p << std::endl;
std::cout<< "ptMyClass::a = " << ptMyClass->GetA() << std::endl;
ptMyClass->~MyClass();
std::cout<< "ptMyClass::a = " << ptMyClass->GetA() << std::endl;
free(p);
std::cout << "******************************" << std::endl;
ptMyClass = new(macBuffer) MyClass("");
std::cout << "&ptMyClass ==>" << ptMyClass << std::endl;
std::cout << "&macBuffer ==>" << (int*)(macBuffer) << std::endl;
ptMyClass->~MyClass();
// delete ptMyClass; 2
}
![](https://i-blog.csdnimg.cn/blog_migrate/905ca890f0355792bfd515807185ddf5.png)
这里我们可以看到,对象的内存地址和我们事先申请的内存地址是一样的。对于
我们实现分配的内存大小,官方说要用对象大小+sizeof(int)来存储,但是我试了一下用对象大小的存储空间也可以,其次是该存储空间的释放,就按照该存储空间的类型相对应的方法取释放,局部的当出了作用域后自动释放,堆空间手动释放等等,另外当我们调用placement new构造的对象,在对象的销毁时需要我们手动调用析构函数,此时会清空内存中的内容,并不会释放该对象的内存空间,观察调用析构函数调用前后的打印区别可知。
其次我们要注意一点,我们通过placement new构造的对象,不能调用delete来释放内存,因为我们构造时其实是没有申请内存的,只是调用了构造函数,返回了一个指针指向了我们事先准备好的内存空间,当我们使用delete编译器会试图去删除对象在堆上的内存空间,但是此时的对象并不在堆上,可能在栈上或者在静态存储区(每个存储区的地址范围不同,我们很可能拿着一个栈上的地址去堆上删除),此时程序会挂掉(注释2)。
最后我们要注意的是如果我们要用这种方式来替换普通的构造对象的方式,我们要保证同意时刻只有一个对象构造,比如开始时说的场景中同一时刻只能处理一个消息,也就是说是一个单进程单线程的模型,当我们处理完一个消息后,这个消息对于我们没有意义了,当下一个消息来时,我们就可以重新构造对象了,否则就会出现我们正在使用的对象被修改的的现象,当然我们也可以每个线程一块内存空间。