Effective C++ 第二版 10) 写operator delete
条款10 写了operator new就要同时写operator delete
写operator new和operator delete是为了提高效率;
default的operator new和operator delete具有通用性, 也可以在特定情况下被重写以改善性能; 特别在需要动态分配大量的很小的对象的应用程序中;
|
1
2
3
4
5
6
7
|
class AirplaneRep { ... }; // 表示一个飞机对象
class Airplane {
public:
...
private:
AirplaneRep *rep; // 指向实际描述
};
|
>Airplane对象只包含一个指针, 如果声明了虚函数, 则会隐式包含虚指针;
当调用operator new来分配Airplane对象时, 得到的内存可能比存储这个指针所需要的多, 因为operator new和operator delete之间需要相互传递信息;
default版本的operator new是一种通用型的内存分频器, 可以分配任意大小的内存块; operator delete也可以释放任意大小的内存块;
operator delete需要知道要释放的内存多大(operator new分配的内存大小) e.g. 在operator new返回的内存里附带额外信息, 指明被分配的内存块的大小;
Airplane *pa = new Airplane; 得到的不是: pa——> Airplane 对象的内存; 而是: pa——> 内存块大小数据 + Airplane 对象的内存; 对于小对象, 额外的数据信息会使得动态分配对象时需要的内存大小翻倍;
Solution: 为Airplane类专门写一个operator new, 利用每个Airplane的大小相等的特点, 不需要加上附带信息;
e.g. 先让缺省的operator new分配一些大块原始内存, 每块的大小足够容纳多个Airplane对象, Airplane对象的内存块取自这些大内存块;
当前没有使用的内存块被组织成链表-自由链表, 未来给Airplane使用; rep域的空间被用来存储next指针;
修改Airplane支持自定义的内存管理:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Airplane { // 修改后的类 — 支持自定义的内存管理
public:
static void * operator new(size_t size);
...
private:
union {
AirplaneRep *rep; // 用于被使用的对象
Airplane *next; // 用于没被使用的(在自由链表中)对象
};
// 类的常量,指定一个大的内存块中放多少个Airplane 对象,在后面初始化
static const int BLOCK_SIZE;
static Airplane *headOfFreeList;
};
|
>operator new函数, union(rep和next占用相同空间), int指定大内存块大小, static指针(跟踪自由链表的表头) - 整个类只有一个自由链表;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
void * Airplane::operator new(size_t size)
{
// 把“错误”大小的请求转给::operator new()处理; 详见条款8
if (size != sizeof(Airplane))
return ::operator new(size);
Airplane *p = headOfFreeList;// p 指向自由链表的表头
// p 若合法,则将表头移动到它的下一个元素
if (p)
headOfFreeList = p->next;
else {
// 自由链表为空,则分配一个大的内存块,可以容纳BLOCK_SIZE 个Airplane 对象
Airplane *newBlock = static_cast<Airplane*>(::operator new(BLOCK_SIZE * sizeof(Airplane)));
// 将每个小内存块链接起来形成一个新的自由链表
// 跳过第0 个元素,因为它要被返回给operator new 的调用者
for (int i = 1; i < BLOCK_SIZE-1; ++i)
newBlock[i].next = &newBlock[i+1];
// 用空指针结束链表
newBlock[BLOCK_SIZE-1].next = 0;
// p 设为表的头部,headOfFreeList 指向的内存块紧跟其后
p = newBlock;
headOfFreeList = &newBlock[1];
}
return p;
}
|
>这里的operator new管理的内存是从::operator new分配来的, 所以new-handler的处理都在::operator new之中;
|
1
2
|
Airplane *Airplane::headOfFreeList;
const int Airplane::BLOCK_SIZE = 512;
|
>static member的初始值缺省为0;
>这个版本的operator new为Airplane对象分配的内存比缺省operator new的少, 运行更快(2次方等级), 只需操作链表中的一对指针, 用灵活性换速度;
Note 因为通用型的operator new必须处理各种大小的内存请求, 还要处理内部外部的碎片;
需要声明Airplane的operator delete, 因为::operator delete会假设内存包含头信息;
Note operator new 和operator delete 必须同时写;
|
1
2
3
4
|
class Airplane {
...
static void operator delete(void *deadObject,size_t size);
};
|
传给operator delete 的是一个内存块, 如果其大小正确, 就加到自由内存块链表的最前面;
|
1
2
3
4
5
6
7
8
9
10
11
|
void Airplane::operator delete(void *deadObject, size_t size)
{
if (deadObject == 0) return; // 见条款 8
if (size != sizeof(Airplane)) { // 见条款 8
::operator delete(deadObject);
return;
}
Airplane *carcass = static_cast<Airplane*>(deadObject);
carcass->next = headOfFreeList;
headOfFreeList = carcass;
}
|
>new和delete匹配, 如果opertaor new将"错误"大小的请求转给了::operator new, 这里同样要转给::operator delete;
Note 保证基类必须有虚析构;
如果要删除的对象是从一个没有虚析构函数的类继承来的, 那传给operator delete的size_t可能不正确; operator delete有可能工作不正确;
引起内存泄露的原因在于内存分配后指向内存的指针丢失了, 如果没有类似垃圾处理机制, 内存就不会被收回;
上面的设计没有内存泄露, operator delete没有释放, 但是每个大内存块被分成Airplane大小的块, 小块放在自由链表上.
客户调用Airplane::operator new时, 小块被自由链表移除, 客户得到指向小块的指针. 客户调用operator delete时, 小块放回自由链表头上;
所有的内存块要么被Airplane对象使用(客户维护内存), 要么在自由链表上(内存块有指针), 因此没有内存泄露;
::operator new返回的内存块从来没有被Airplane::operator delete释放, 这种内存块叫内存池;
Note 内存泄露会无限增长, 内存池的大小不会超过客户请求内存的最大值;
可以修改Airplane的内存管理使得::operator new返回的内存自动释放, 这里不这么做的原因:
1) 自定义内存管理的初衷.
缺省的operator new和operator delete使用了大多内存, 运行很慢. 和内存池策略相比, 跟踪和释放大内存块所写的每一个额外的字节和每一条额外的语句都会导致软件运行更慢, 内存占用更多; 在设计性能要求很高的库或程序时, 如果预计的内存池大小会在固定的合理范围内, 那采用内存池策略就很好;
2) 和处理一些不合理的程序行为有关.
假设Airplane的内存管理程序被修改了, Airplane的operator delete可以释放任何没有对象存在的大块的内存;
|
1
2
3
4
5
6
7
8
9
|
int main()
{
Airplane *pa = new Airplane; // 第一次分配: 得到大块内存,生成自由链表,等
delete pa; // 内存块空; 释放它
pa = new Airplane; // 再次得到大块内存,生成自由链表,等
delete pa; // 内存块再次空,释放
//...
return 0;
}
|
>这样的小程序比缺省的operator new和operator delete运行的还慢, 占用更多内存.
>内存池无法解决所有的内存管理问题, 但在很多情况下是适合的.
为了给不同的类实现基于内存池的功能, 需要把这种固定大小内存的分频器封装起来:
e.g. Pool类接口, 每个对象是某类对象的内存分配器 (大小在Pool的构造函数里指定)
|
1
2
3
4
5
6
7
|
class Pool {
public:
Pool(size_t n); // 为大小为n 的对象创建一个分配器
void * alloc(size_t n) ; // 为一个对象分配足够内存, 遵循条款8 的operator new 常规
void free( void *p, size_t n); // 将p 所指的内存返回到内存池, 遵循条款8 的operator delete常规
~Pool(); // 释放内存池中全部内存
};
|
>这个类支持Pool对象的创建, 执行分配和释放, 被摧毁的操作; Pool对象被摧毁时, 会释放它分配的所有内存;
>如果Pool的析构函数调用太快, 使用内存池的对象没有全部摧毁, 对象正使用的内存消失, 造成的结果是不可预测的.
内存管理:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Airplane {
public:
... // 普通Airplane 功能
static void * operator new(size_t size);
static void operator delete(void *p, size_t size);
private:
AirplaneRep *rep; // 指向实际描述的指针
static Pool memPool; // Airplanes 的内存池
};
inline void * Airplane::operator new(size_t size)
{ return memPool.alloc(size); }
inline void Airplane::operator delete(void *p, size_t size)
{ memPool.free(p, size); }
// 为Airplane 对象创建一个内存池,在类的实现文件里实现
Pool Airplane::memPool(sizeof(Airplane));
|
>比起之前的设计更清晰, Airplane不再和非Airplane代码混在一起. union, 自由链表头指针, 定义原始内存块大小的常量都归入Pool类里了;
自定义内存管理程序用来改善程序性能, 可以被封装在像Pool这样的类里;
构造, 析构函数和赋值操作符
构造函数控制对象生成时的基本操作, 对象初始化; 析构函数销毁对象, 保证它被彻底清除; 赋值操作符给对象一个新值;
这些函数要保证正确性, 一旦出错对整个类带来的影响是无尽的.
Effective C++ 第二版 10) 写operator delete的更多相关文章
- Effective C++ 第二版 8) 写operator new 和operator delete 9) 避免隐藏标准形式的new
条款8 写operator new 和operator delete 时要遵循常规 重写operator new时, 函数提供的行为要和系统缺省的operator new一致: 1)正确的返回值; 2 ...
- Effective C++ 第二版 5)new和delete形式 6) 析构函数里的delete
内存管理 1)正确得到: 正确调用内存分配和释放程序; 2)有效使用: 写特定版本的内存分配和释放程序; C中用mallco分配的内存没有用free返回, 就会产生内存泄漏, C++中则是new和de ...
- 条款十: 如果写了operator new就要同时写operator delete
为什么有必要写自己的operator new和operator delete? 答案通常是:为了效率.缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也 ...
- Effective Java 第二版 Enum
/** * Effective Java 第二版 * 第30条:用enum代替int常量 */ import java.util.HashMap;import java.util.Map; publi ...
- Effective C++ 第二版 17)operator=检查自己 18)接口完整 19)成员和友元函数
条款17 在operator=中检查给自己赋值的情况 1 2 3 class X { ... }; X a; a = a; // a 赋值给自己 >赋值给自己make no sense, 但 ...
- Effective C++ 第二版 40)分层 41)继承和模板 42)私有继承
条款40 通过分层来体现"有一个"或"用...来实现" 使某个类的对象成为另一个类的数据成员, 实现将一个类构筑在另一个类之上, 这个过程称为 分层Layeri ...
- Effective C++ 第二版 31)局部对象引用和函数内new的指针 32)推迟变量定义
条款31 千万不要返回局部对象的引用, 不要返回函数内部用new初始化的指针的引用 第一种情况: 返回局部对象的引用; 局部对象--仅仅是局部的, 在定义时创建, 在离开生命空间时被销毁; 所谓生命空 ...
- 《Effective Java第二版》总结
第1条:考虑用静态工厂方法代替构造器 通常我们会使用 构造方法 来实例化一个对象,例如: // 对象定义 public class Student{ // 姓名 private String name ...
- Effective C++ 第二版 1)const和inline 2)iostream
条款1 尽量用const和inline而不用#define >"尽量用编译器而不用预处理" Ex. #define ASPECT_R 1.653 编译器永远不会看到AS ...
随机推荐
- MVC打开电脑对话框
//下载文件 public ActionResult Download(int id) { //依靠模板生成文档 var path =要下载的文件的路径 var name = Path.GetFile ...
- js处理json的方法
var json = "{id:"myid", url:"http://www.myurl.com"}"; var js= (new Fun ...
- input 输入框获得/失去焦点时隐藏/显示文字(jquery版)
input输入框在获得或失去焦点时隐藏或显示文字,这样的焦点效果想必很多朋友在填写form表格的时候都曾见识过吧,本文使用jquery实现以下,感兴趣的朋友可以参考下哈 大家可以看效果图的搜索输入框, ...
- 2)PHP中把读取.txt中内容并转为UTF-8格式
<?php $filename = "filename.txt"; $handle = fopen($filename, "r");//读取二进制文件时, ...
- 生成输出url
继续使用前面的例子11-3URLTestDemo,修改Global.asax中的RegisterRoutes方法如下: public static void RegisterRoutes(RouteC ...
- Oracle ODI系列之一(ODI知识模块)
Oracle ODI系列之一(ODI知识模块) ODI简介 ODI(Oracle Data Integrator)前身是Sunopsis Active Integration Platform ...
- FTL(Flash translation layer)闪存转换层
前面说过,闪存的读写单位为页,而页的大小一般为4KB或8KB,但我们的操作系统读写数据是按HDD的扇区尺寸进行的(512Byte(字节)),更麻烦的是闪存擦除以块作单位,而且未擦除就无法写入,这导致操 ...
- Codeforces 353D Queue(构造法)
[题目链接] http://codeforces.com/contest/353/problem/D [题目大意] 10^6个男女排队,每一秒,如果男生在女生前面,即pos[i]是男生,pos[i+1 ...
- Is it possible to implement a Firebug-like “inspect element” DOM element highlighter with client-side JavaScript?
Is it possible to implement a Firebug-like "inspect element" DOM element highlighter with ...
- 假设给Contact的List加一个用字母排序的导航
效果图: 这样写Layout: <? xml version="1.0" encoding="utf-8"? > <LinearLayout ...