OOC
Object-oriented design patterns in the kernel 1,多态部分
Object-oriented design patterns in the kernel 2,继承部分
下载保存
https://www.cs.princeton.edu/courses/archive/spring21/cos217/
https://www.cs.princeton.edu/courses/archive/spring21/cos217/lectures/
1 封装
- 参考文件操作 FILE*
- 使用不完全类型
- 头文件分成 2 个,一个对外接口,一个用来给子类嵌套继承
2 多态
两种实现方式,常用第 2 种,文章对内核种第 2 种使用的特殊情况做了讨论
- 最简单的方法是结构体包含函数指针,函数第一个参数为结构体指针。bar->foo(bar, …args)。但在对象很多时由于内存占用比下面的 vtable 多,这种 linux 内核不常用
- 再复杂是将函数指针组成表,virtual function table (vtable) ,一般命名为 XXX_operations、XXX_ops,对象中包含这个表的指针
- vtable 为 NULL 的 2 个原因
- 新添加了一个方法,但还没实现
- 该方法对这种情况无意义,临时的或长期的
- 但作者认为总可避免使用 NULL(个人观点:会掩盖 Bug)
- NULL 是少数时,实现默认方法,利用 C99 多次初始化,将默认值写在前面
- NULL 是多数时,且性能很重要,在调用前判断 flag 的 bit,出现特殊情况才调 vtable 中的函数
- vtable 还可包含模块名称和链表 struct list_head,便于上层查找注册的设备
- vtable 第一个参数不是结构体指针的情况
- 结构体指针放在了最后一个参数
- 只有一个对象,所以不需要指针,这个对象就是全局的
- 多重继承,Mixin,提供某个功能,但不以这个对象为主,需要多个对象
- 子类的操作放入父类的 vtable 中,避免数量多的结构体过大,节省内存,以 struct page 举例
- vtable 为 NULL 的 2 个原因
3 继承 is
派生类包含基类 | 基类使用 void *private 指针 |
基类包含派生类 | |
---|---|---|---|
定义 | 基类是第一个派生类成员 | 基类和派生类互相指 | 基类和派生类定义到一个文件 派生成员 union 实现 把每个使用 union 的地方看作继承 |
创建 | 调派生类 ctor,派生类 ctor 再调基类 ctor | 先调基类 ctor,基类 ctor 再调派生类 ctor | 先调基类 ctor,再调派生类 ctor |
使用 | 对外提供基类指针,用多态方式调派生类接口 派生类接口中用 container_of() 将基类指针转成派生类 |
/ | / |
优点 | 多数情况 | 适用于需要切换子类的类型时 | / |
缺点 | / | 两次内存分配 性能不好,内存占用和解引用花费时间 void * 可读性差,看不出含义 |
浪费内存 |
mixin 类型继承,例如链表,提供某种服务
一个对象只有一个引用计数(在最内层基类中)管理生命周期,内核不使用垃圾回收,因此可用是否有引用计数将单继承与 mixin 继承区分开
TODO,下一步看 linux 内核加密部分、openssl 部分代码,复习 ooc 代码,仿写出 modbus 代码
重点在第 6 章,后续章要么太难,要么与主题无关,所以基本没看。此外那个计算器是个有趣、有难度的例子。这本书使用 void* 而没用不完全类型(即前向声明)是个小瑕疵,否则代码会更清晰
添加新函数时,基于 vtable 基类 Class 扩展新的函数,定义好后,new 一个新的 vtable 对象(这其实分两步,见第 6 章 initPoint() 代码),所谓类描述符。
添加新成员时,基于 Object 扩展,然后用刚刚的 vtable 对象(即类描述符)创建对象(因为刚刚创建的 vtable 包含了构造函数和对象大小)。因此创建对象要先创建类描述符。
因此对于成员和函数要有两个基类,vtable 因为只有一个且储存了对象大小信息所以叫 Class(为避免歧义称 Class 类为 vtable 类,或者叫类描述符类),而包括 vtable 类在内的所有类都继承自 Object 类,Object 类里面只有一个 Class 指针成员,表明所有对象都属于某个类。还要静态创建 Class 和 Object 类的类描述符,用于子类的 ctor 和 dtor 中调用,初始化基类成员部分。对于 Object 的 ctor 什么都不用做, Class 的 ctor 初始化函数指针。
改进和个人思考:这里静态创建的 Class 和 Object 类的类描述符,用来作为 ctor 调父类 ctor 的终点,完成继承关系即 新类的 vtable -> Class -> Object 。新的类描述符要 new 出来,不如静态定义清晰,因为类不会像对象创建很多(虽然逻辑上确实是用 Class 这个 vtable 基类,创建新的类,相当于 new 新的类描述符对象,但这种对象不会很多)。如静态定义就不必再调 Class 的 ctor,也不必静态创建 Class 的类描述符。而且 Class 不必继承 Object,否则每个对象,成员部分继承 Object 有 Class 指针,函数部分也继承 Object,即 Class 部分还要有 Object,Object 里面又有 Class。Class 不继承 Object(即不包含 Object),自己本身就作为 vtable 形式的基类,与 Object 是平等的关系。新类的 ctor 没必要再调父类 Object 的 ctor,可以仅为了继承逻辑,struct Class Object = {初始化},新类指向 &Object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28struct Object {
const struct Class * class; // 没变化
};
struct Class {
// 这里去掉 Object 继承,const struct Object _;
const char * name;
const struct Class * super;
size_t size;
void * (* ctor) (void * self, va_list * app);
void * (* dtor) (void * self);
int (* differ) (const void * self, const void * b);
int (* puto) (const void * self, FILE * fp);
};
struct Class Object = {.name = "Object"}; // 只定义 Object 的类描述符,不再定义 Class 的类描述符
struct Point {
const struct Object _;
int x, y;
};
struct PointClass { // 静态初始化类描述符
const struct Class _;
void (* draw) (const void * self);
} Point = {_.name = "Point", _.super = &Object, _.size = sizeof(struct Point), _.ctor = Point_ctor, .draw = Point_draw};
p = new(Point, 1, 2); // new 里面按照 Point 的大小申请内存和调构造函数进一步举例如何应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19struct DeviceClass {
struct Class parent;
void (* fun1) (const void * self);
} DeviceDesc = {这个就是类描述符,创建对象时需要} ;
struct Device {
struct Object parent;
int x, y;
};
struct SubDeviceClass {
struct DeviceClass parent;
void (* draw) (const void * self);
} SubDeviceDesc = {这里可以先继承父类的函数指针(.parent = DeviceDesc),然后在后面来覆盖(.parent.parent.ctor = SubDeviceClass_ctor),因为根据 C99 多次初始化最后那个有效};
struct SubDevice {
struct Device parent;
int z;
};
4 异常处理
用对应的库,用 setjmp 和 longjmp 实现。
还能实现线程切换,已经放入 Dropbox 书 https://www.cs.princeton.edu/courses/archive/spring04/cos217/lectures/Exceptions.pdf
5 单元测试
试试 Google Test ? https://stackoverflow.com/questions/65820/unit-testing-c-code
6 内存管理
7 安全
cppcheck