在C++中使用new来进行内存分配和对象初始化。最常见的做法,当我们new一个对象时:
X *px = new X();
编译器会生成类似如下形式的代码:
void *memory = operator new(sizeof(X));// 得到未经处理的内存
call string::string() on *memory; // 调用初始化函数
X *px = static_cast<X*>(memory); // px指针指向生成的对象
整个对象的生成过程可以细分为两个步骤:1)分配内存;2)在分配的内存上调用构造函数,完成初始化。这个过程的前后步骤是不可变的,但是其中的内存分配和构造函数,我们是可以自定义的。(跟设计模式中的模板模式比较像)。
首先区分几个概念:
new operator:new操作符,可以理解为关键字、操作原语,我们在代码中使用它来生成一个对象或者对象数组。
operator new:分配内存的函数,用户可以自己重定义。
placement new:可以认为是operator new的一个特例,它比operator new多一个内存地址作参数。
因此,自定义内存分配,有两种new操作:
new operator = operator new + constructor
new operator = placement new + constructor
注:两种方法new operator是不同的。后者要多一个参数。
一、operator new + constructor
这种情况下,只需要自己重新实现operator new即可。编译器对new operator进行扩展,调用用户自己定义的operator new进行内存分配,然后调用构造函数。
调用代码:
X *px = new X();
重写operator new:
void * operator new(size_t size) {
...
return 内存地址;
}
在嵌入式开发中,就会经常使用这样的方式:预先分配好固定的内存,用链表进行保存。operator new中只需要查找free list即可。这样做,一般有如下几个好处:
1)节省内存空间,可以省掉编译器分配内存时添加的附加信息(该信息保存给给delete释放内存时使用)。
2)省掉了搜索内存表的时间,加快了内存分配速度。并且避免了内存碎片产生。
二、placement new + constructor
与前面一种不同,有时我们有已经分配好的内存,只需要调用构造函数初始化即可,在这种情况下,我们会使用到placement new。它定义在<new>中,实现很简单:
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
仅仅是将传入的内存地址返回而已(因为内存已经分配好了)。
调用代码:
char *buffer = new char[size];
X *px = new (buffer) X();
其中buffer为已经分配好的地址。当遇到上面的语句时,编译器会扩展语句,调用placement new,然后在内存上调用构造函数。
注意,这种情况下的内存释放,需要手工调用析构函数和释放内存操作:
px->~x();//调用析构函数
freeMemory(buffer);//伪代码,释放内存buffer
不能直接写:
delete px;
因为这样,编译器会扩展成:
px->~x();
operator delete(px);
其中,operator delete是没有定义的。
自定义内存分配时,需要注意的问题:
1. 如果重写 operator new,同样需要重写 operator delete,后者负责内存释放。
2. 如果重写 operator new,同样需要重写 operator new[]和 operator delete[],后者是针对数组。
X *px = new X[10];
delete[] px;