浅谈C++ allocator内存管理(对比new的局限性)
STL中,对内存管理的alloc的设计,迫使我去学习了allocator类。这里对allocator内存管理做了点笔记留给自己后续查阅。allocator类声明、定义于头文件<memory>中的std命名空间内。所以,应该有以下内容位于文件头部…
#include <memory>
using namespace std;
1
2
文章目录
1. 我们所知道的malloc和new
2. C++中new的局限性
3. 使用allocator将内存分配、对象构造分离开
1. 我们所知道的malloc和new
再此之前,我只知道两种开辟内存的方式。
其一,可以使用C语言的函数malloc、realloc、calloc开辟内存,举个例子:
int* ptr = (int *)malloc(10 * sizeof(int));/* 进行强制类型转换 */
1
其二,可以使用C++方式开辟内存,比如:
int* ptr = new int[10];
1
对于,C风格的内存管理这里不做讨论。但是,对于C++的new风格,这里总结一下它的局限性。对于我个人来说,我一般写成这样new int[10](),也就是在最后加一对小括号,因为C++保证这样可以将int[10]中内存全部初始化为0。
2. C++中new的局限性
对于以上这一段话,有一个话题需要弄清楚的,就是——内存构造。
对C++的new而言,它首先会(1)分配内存,然后自动的完成(2)对象构造。这里可以用侯捷先生翻译的《深度探索C++对象模型》一书中的伪代码来表示new的过程:
Point* heap = __new(sizeof(Point));//开辟内存
if (head != 0) {
head->Point::Point();//对象构造(内存构造)
}
1
2
3
4
注意,__new不表示new(它只是完成内存申请),以上整个伪代码过程为new所完成的功能。
正是因为new的这一连串的操作,造成了性能的下降。比如,
auto p = new string[100];
for (int i = 0; i < 5; ++i){
p[i] = "balaba...";
}
1
2
3
4
实际上,我只需要5个string,而new把100个对象全部构造好了(每个string已经被初始化为空字符串,也就是"")。
然后,接着又将p[0-4]赋值为balaba…
也就是前面将p[0-4]赋值为空字符串的操作,变得毫无意义。
3. 使用allocator将内存分配、对象构造分离开
既然,new有它自身的局限性。对于性能要求极高的STL肯定是不会使用new的。好在有一个allocator类——它也是一个模板类,同时就是用来处理内存问题的。
allocator类将new的内存分配、对象构造,视作两个独立的过程,并由独立的函数负责。举个例子:
allocator<char> str;
char* base = str.allocate(10), *p = base; //内存分配
str.construct(p++, 'a'); //对象构造并初始化
str.construct(p++, 'b');
cout << base[0] << base[1];
1
2
3
4
5
因为allocator是模板类,所以需要指定类型。接着,调用allocate(10)函数来分配内存(申请了10个char内存)。然后,使用construct函数构造base[0]这块内存,并赋以初值a。
这就将new内存分配、内存构造给分离开了。一切,都像我们看到的那样。
同样,将delete的过程也拆分了开来。这是必须的,我们不能用delete去释放allocate分配的内存。
str.destroy(--p); //销毁对象
str.destroy(--p);
str.deallocate(base, 10); //释放内存
1
2
3
小结:如果要使用allocate返回的内存,那么就必须先construct构造它。否则,你后续的行为都是未定义的(造成的后果也是严重的)。
但是,有几个例外。它们是uninitialized_copy、uninitialized_copy_n、uninitialized_fill和uninitialized_fill_n。从名字就知道uninitialized(未初始化的),它们的参数必须指向的是未构造的内存,比如uninitialized_copy会在给定位置构造元素。
---------------------
作者:qingdujun
原文:https://blog.csdn.net/qingdujun/article/details/85224771