非常建议先看#0x0 w~
目标
在这一节中,我们希望能够实现类似C++中一样,能一键new和delete一个对象。
同时,我们也希望能构造一个通用的“类”模板,以便规范我们的编写和调用。
对此,我们需要:
搞清楚一个类的原型需要些什么
实现在程序中“注册”一个“类”
成功new一个对象
实现一系列的函数,以便代替操作符进行一个统一的调用
那么,让我们开始吧~
一个类的原型
为了让所有的类看起来相似,同时也是为了更方便的注册一个类,我们需要首先规定一个类必须要存储哪些信息。
对此,我们不妨先来一个结构体存吧~
1 2 3 typedef struct CLASS_T { } class ;
首先,我们需要知道如果要新建一个对象的话,需要多少的空间。也就是说,我们需要存储一个类的大小。
于是我们加上const size_t type_size;
接下来,为了能统一所有类的构建,我们并不希望 生成一个对象的时候,都要去调用这个类的构造函数(如你要一个cat对象,就要调用cat_constructor;一个dog对象就要dog_constructor)。
我们期望的是像C++那样,可以直接new(cat)、new(dog)。对此我们必须要存储该类的构造函数。
但是,我们先停停,想一想这个构造函数应该传入哪些东西!
应该传入this吗?
或许你的答案是不用吧,毕竟我们应该让这个构造函数创建存储区,并返回已经建好的指针,而不是自己去创建一片区域吧?
但是,这里忽略了一个华点!如果一个子类想要调用父类的构造函数的话,那不就GG了喵——
所以,一个类的存储空间应该在外部声明,然后传this进去!
应该定义多少个参数合适?
答案是:都不合适!
毕竟要是多了的话,勉强还可以NULL处理;要是少了的话……GG——
所以,为什么不用人见人爱的va_list呢?
但是,直接...
是不行的!(因为如果想要实现类似new的功能的话,就必须在new的内部来传入参数了,但是new函数不知道有几个参数啊!QAQ)
一个讨巧的方法是:把va_list指针传进去,想到了喵~
类似的,我们也要声明复制构造和析构函数
1 2 3 void (*constructor)(void *_this, va_list *ap_p); void (*destructor)(void *_this); void (*deep_copy)(void *_this, void *_that);
接下来……我们再来尝试实现一下它的一些默认函数吧。对此,我们不妨参考一下Python 是怎么做的w
在此,我们只定义部分基础的东西,毕竟当前阶段还没必要写太多~
首先,我们为了实现一点点的模板多态性,来一个iter吧~class *iter;
(为什么这里的iteration是一个类的原型而非函数呢)
然后我们再来加上大部分的数学运算符ouo
这里有一个需要注意的点!
为了实现多态,我们一般而言的解决方法是:传入一个指针,像这样:void *(*operator_add)(void *_this, void *_that);
这样是很好啦~
但是它只能处理左值,而不能处理右值(悲
那么,有没有什么东西不管什么都能传入呢?没错,va_list
当然,这要求我们完全的信任程序员,毕竟一旦写成…,传入什么参数,传入多少个参数都是只能交给程序员自己控制了~
于是,我们便将函数声明改成这样:void *(*operator_add)(void *_this, va_list *ap_p);
在定义比较运算符的时候可以取个巧:只用定义gt和eq,ge=gt||eq
,lt=!(gt||eq)
,le=!ge
于是,我们现在便可以给出一个完整的函数原型了~
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 28 29 30 31 32 33 typedef struct CLASS_T { const size_t type_size; void (*constructor)(void *_this, va_list *ap_p); void (*destructor)(void *_this); void (*deep_copy)(void *_this, void *_that); void *(*operator_add)(void *_this, va_list *ap_p); void (*operator_iadd)(void *_this, va_list *ap_p); void *(*operator_min)(void *_this, va_list *ap_p); void (*operator_imin)(void *_this, va_list *ap_p); void *(*operator_mul)(void *_this, va_list *ap_p); void (*operator_imul)(void *_this, va_list *ap_p); void *(*operator_div)(void *_this, va_list *ap_p); void (*operator_idiv)(void *_this, va_list *ap_p); void *(*operator_lshift)(void *_this, va_list *ap_p); void *(*operator_ilshift)(void *_this, va_list *ap_p); void *(*operator_rshift)(void *_this, va_list *ap_p); void *(*operator_irshift)(void *_this, va_list *ap_p); void *(*operator_and)(void *_this, va_list *ap_p); void *(*operator_iand)(void *_this, va_list *ap_p); void *(*operator_or)(void *_this, va_list *ap_p); void *(*operator_ior)(void *_this, va_list *ap_p); void *(*operator_xor)(void *_this, va_list *ap_p); void *(*operator_ixor)(void *_this, va_list *ap_p); bool (*operator_gt)(void *_this, va_list *ap_p); bool (*operator_eq)(void *_this, va_list *ap_p); class *iter ; } class ;
实现new、delete和copy
new
我们期望new应该能这么使用:new(类名,(参数))
而在new函数之内一般要做这么几件事:分配空间,调用构造函数,返回对象指针
于是,之前埋的伏笔终于有用武之地了!对象所需空间和构造函数在函数原型理都有诶——我们只需要简单的把函数原型的结构体就命名为类名就好了诶——
同时,我们也相当于注册了一个类~
在此给出new的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void *new (const class *_class, ...) { class_base *res = malloc (_class->type_size); if (_class->constructor) { va_list ap; va_start(ap, _class); _class->constructor(res, &ap); va_end(ap); } res->self_class = _class; return res; }
delete
对于delete期望这样使用:delete(对象指针)
里面就会自动的调用析构函数了w
但是这里有个小问题:我们传进去的是this指针,而不是函数原型。该怎么找到析构函数呢?
我们当然可以把析构函数拷贝到对象里,也可以通过某种数据结构关联。但或许我们可以把函数原型的指针也存进去不就好了嘛,还能顺便解决运算符调用的问题。
于是我们声明一个基类,并要求所有的类都继承该基类 ,然后在构造时赋值即可(伏笔回收~)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef struct CLASS_BASE_T { class *self_class ; } class_base; void delete (void *_this) { const class_base *this = _this; if (this->self_class->destructor) { this->self_class->destructor(this); } free (this); }
copy
复制构造。会自动复制this的内容并返回一个新的对象。这里直接给出代码~
1 2 3 4 5 6 7 8 9 10 11 12 void *copy (const void *_this) { const class_base *this = _this; class_base *res = malloc (this->self_class->type_size); memcpy (res, this, this->self_class->type_size); this->self_class->deep_copy(this, res); return res; }
两个奇怪的函数
由于子类在调用父类的构造、析构函数时。用new是不行的。对此,我们新定义两个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 void constructor (const class *_class, void *_this, va_list *ap_p) { const class_base *this = _this; _class->constructor(this, ap_p); } void destructor (class *_class, void *_this) { const class_base *this = _this; _class->destructor(this); }
统一的运算符
这里的思想其实都是上面用过的,对此我就给出add
和iadd
来抛砖引玉咯~
(你要完整版代码的话,仓库自取~)
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 28 29 30 void *add (void *_this, ...) { const class_base *this = _this; va_list ap; va_start(ap, _this); if (this->self_class->operator_add) { return this->self_class->operator_add(_this, &ap); } va_end(ap); return NULL ; } void iadd (void *_this, ...) { const class_base *this = _this; va_list ap; va_start(ap, _this); if (this->self_class->operator_iadd) { this->self_class->operator_iadd(_this, &ap); } va_end(ap); }
尾声
希望以上的东西能给你带来启发,要是能学到新的东西就再好不过了~
下一章,我们来尝试实现一个重要的基础类:iterator