注意:这篇文章上次更新于1572天前,文章内容可能已经过时。
This article was last updated1572 days ago, the content may be outdated.
C++ 中的 new
C++ 中使用 new 关键字来创建一个对象,创建对象的过程会产生以下操作:
- 在内存中申请一块空间,大小为目标对象的大小,并获得该内存空间的地址。
- 将该内存空间的地址转换为对象指针。
- 通过对象指针调用该对象的构造函数。
对于第一步来说,C++如何获得内存空间的呢?
答案是通过调用operator new(size_t size) 函数,其参数为要申请的空间大小,内部实现依然是通过 C 语言的 malloc 函数。值得一提的是,这个函数是可以进行重载的,这就为我们的内存管理留下了可操作空间。
默认情况下,new 表达式调用的是全局的 ::operator new(size_t size) 函数来申请内存空间,当我们在类中定义了成员函数 operator new(size_t size),编译器就会调用类中的成员operator new 函数来分配内存空间,所有内存管理的操作,也可以在成员operator new 函数中来实现。
delete 与 new 同理,会先通过指针调用该对象的析构函数,再调用 operator delete 函数来释放内存空间,其中 operator delete 也分为全局函数和成员函数,也支持重载,全局 operator delete 函数内部使用 C 语言的 free 来释放内存空间。
用代码表示为:
1 | Foo* p = new Foo(x); // new 表达式,不可改变,不可重载 |
上面这一行代码会被转化为:
1 | Foo* p = (Foo*)operator new(sizeof(Foo)); // 调用 operator new 函数申请内存空间 |
我们所面临的问题是,当用户需要大量的创建 Foo 对象时,需要重复的反复的调用 malloc 函数来申请内存空间,这会带来两个问题:
- 调用 malloc 的次数太多,从而影响效率。(在 C++ 中申请和释放内存空间最终都是通过使用 C 语言的 malloc 和 free 函数来实现。其实 malloc 函数的执行速度并不慢,内存管理的目的也不是为了减少 malloc 的次数,当然,即使 malloc 函数的效率很高,减少它的调用次数也依然是好事情。)
- 浪费空间,C++为每个对象申请到的内存空间都会使用 cookie 来标志起始和结束位置,每个cookie各占用 4 个字节,如果你的对象数据成员很少的话,这将会是很大比例的浪费。
为了解决上面那两个问题,我们就需要定义成员operator new 和operator delete 函数,让 new 和 delete 的行为有我们自己控制。
内存管理的一些例子
第一个例子取材于 C++ Primer。
这个例子通过成员operator new 函数实现一次性申请足够大的空间(只有两个 cookie),在创建对象时将申请到的空间拿出一部分来构造对象。为了维护空间内的对象,为对象附加一个指针变量,使其形成链表的形式。这就是内存池的思想。

1 |
|
这个版本的代码实现了基本的内存池,通过单向链表的方式维护了内存池,这种方式需要在类中专门定义一个用来维护内存池的指针,在本例中,用来维护内存池的数据成员和类原始数据成员一样大,显然一定程度上造成了空间的浪费。
下面的代码采用内嵌指针的方式解决上面代码中的问题。
1 | class Airplane{ |
在上面例子中,使用嵌入式指针的方式避免了额外的空间开销。
想象这样一种情况,使用者 new 了 10000 个对象,然后 delete 了 5000 个,又 new 了 6000 个,又 delete 10000 个…
这种情况下,每次释放的空间会被插入到链表的头部,反复这样操作会出现一个链表长度的峰值,即使实际对象数量小于链表的长度,使得链表中有很多空闲空间,这样的情况也不算是内存泄漏。
当然,如果这些空闲的空间能想办法还给操作系统就更好了。
上面版本的内存管理操作已经很好了,但是对于不同的需要内存管理 class 都需要写一个这样的函数,为了避免过多的重复性的代码,我们把上述内存管理操作封装成类 allocator ,这样在需要内存管理的类中包含一个 allocator 对象即可。
1 | class allocator{ |
上面的设计比前两种的设计都干净多了,不再需要在分配内存的时候进行指针的类型的各种转型。
进一步可以发现,每个需要内存管理的类需要添加的内容依然很有规律性,它需要添加下面这部分代码:
1 | public: |
于是为了更加的简洁,我们可以把上述代码写成宏的方式。
1 |
|
上面所有的例子中利用一条链表来维护内存空间,将其发展为 16 条链表,即下图的形式,并且不再以 class 内的 static 成员出现,而是一个全局的 allocator,这就是 std::alloc 的雏形。



