前面我们提到,不可以使用指针直接调用构造函数,这是编译器做的事情。
但如果想在已分配的空间上构造对象,就要使用placement new
具体用法如下:
这时已经new了一块空间,再使用new(buf)就可以在已有的空间上建立对象。
究其实现,其实调用placement new其实与new的步骤几乎无差别。
也分为调用operator_new,类型转换和调用构造函数。
而真正的区别在于重载了operator_new(),使其直接返回这块空间,相当于什么都没做。这样就可以实现直接使用构造函数的功能了。
根据上述知识,我们在调用new和delete的时候,会调用到operator_new()和operator_delete()
operator_new()和operator_delete()都是可以重载的。
而如果使用全局的operator_new()和operator_delete()方法,一般很少重载(因为会影响全局),按照原生的operator_new()和operator_delete()来调用,那么就会调用到malloc和free
如果是在成员函数中调用operator_new()和operator_delete(),那么可以进行重载。
为什么要重载呢?可能设计者觉得malloc分配的效率不够高,可能觉得malloc的上下cookie比较浪费空间。
当然这些是否属实我们在研究malloc时就会清楚。
重载后,就可以按照自己的分配方式实现内存分配
最后我们就可以得到应用程序分配内存的两条途径:
容器我们知道在STL中有分配器这样管理内存分配的角色。
而容器在使用分配器时,分配器的实现也有一些区别。
有些分配器使用了全局的operator_new()和operator_delete()来实现,也就是在底层调用malloc和free。
而有些分配器则根据自己的规则进行内存分配,如gnu中大名鼎鼎的alloc。
全局的operator_new()和operator_delete()方法重载需要注意,因为影响比较大,可能会出现一些意想不到的情况。所以一般不建议重载。
局部局部重载一般发生在类中。
如果类对象调用new和delete,则会优先考虑局部重载的operator_new()和operator_delete()
重载时,标准的operator_new()中有size_t长度作为参数。
标准的operator_delete()中有void*表示指向这块空间的指针,size_t是长度(可选项)
重载时需要注意,这两个方法必须是静态方法。因为是在创建对象时调用,如果还没有对象那么非静态方法就无法调用。
operator_new[]和operator_delete[]operator_new[]和operator_delete[]也可以重载,主要区别在于要根据数组的长度来增加分配空间的长度。
这里说的重载new()与delete(),并不是我们前面的new与delete(要区分好)
我们可以在类中重载operator new(),但每个版本首先要保证参数列表不同,并且第一参数必须是size_t,其余参数出现在new()的()中。
这就与前面所提的placement new一致了。其实placement new就是一种重载实现。(当然这里是在类中,只能模拟标准库的实现)
而operator delete()的重载就不太一样了。
这些重载不会被delete调用,只有当重载的new在分配内存时抛出异常后,才会去找重载的delete(),看看有没有什么补救的办法。
因为operator_new()和operator_delete()可以重载,所以设计者们在实现自己的分配器上下起了功夫。
自定义分配器的思路就是:减少malloc的使用次数,从而减少cookie(因为malloc会分配上下cookie)。
类中的分配器1既然想要减少malloc的调用,那么重载时应该先分配一大块空间,再进行切割。
这里的chunk就是一大块空间,然后一块一块串起来。
这样只会在分配大块空间时调用malloc,只有头尾两个cookie
通过测试可以看到,左边分配的内存空间间隔只有8,而右边使用全局的方法间隔则是16(因为每个对象都有上下cookie)
这里想要串起来,就需要next指针
而如果在类中增加一个next指针,那么就会增加4字节的空间,反而增加了消耗。
而在operator_delete中,是直接将要回收的块插到了链表的开头。
并没有真正delete,但不影响,因为还在掌控之中。
类中的分配器2为了解决next指针占用空间的问题,在第二个版本中做了一些优化。
很巧妙地使用了联合体结构。也就是嵌入式指针
这种结构不影响本身的成员数据,但又可以在分配时用前4个字节当指针来进行串连。
最终也变成了链表形式,但没有额外的消耗。
静态分配器当不同的class都在重载同样的方法时,设计者就在思考,能不能把它们抽出来,包装成一个大家都可以使用的class
这样就实现了一个静态类
allocate和deallocate方法与之前的类似。
在其他类中使用时,可以直接调用静态类的分配方法:
干净整洁。
用宏实现静态分配器除了直接给出静态分配器,还有一种用宏实现的方式,稍作了解即可。
这样也可以在调用时使用宏
=default与=delete我们在构造与析构时遇到过这两个关键字,其实在内存分配时也可以使用。
比如=delete就可以加在重载的分配函数后面,使其失效。
当然这样做也会运行失败。



