跳到主要内容
版本:master

class_map

简介

class_map类型主要实现了类似C++/java的类,实现比较简单,只支持单继承,做这个的主要目的有两个:

  1. 字段明确,好维护
  2. 类型明确,好优化

说明

  • 我们目前的模式是所有数据集合都放在map中,无论简单的,复杂的,易变的还是不变的,这样的好处是很灵活,我们增加key,减少key,代码都不需要变动,只要生成的地方和使用的地方约定好即可
  • 灵活的同时也带来了坏处,时间一久,内容一多,大家都不清楚某个函数传递的map中都有些什么数据,某个对象身上的map中都有些什么数据,只能在runtime的时候去打印,或者来回看代码,很容易出现错漏。
  • 很多情况下,一些对象身上的map和函数传递时的map都是比较固定的,如果能有类似struct的数据结构来保存和传递会更易维护,如果IDE支持得好,补全提示也比较好做
  • 后续JIT优化的时候,类型都是明确的,不需要预测,会比较好做优化。

注意

  • 在使用class_map时,请尽可能保证类型明确。这样编译器可以在编译时尽可能做好类型的检查和约束。

示例

test_class_map.gs
#include "test_util.gsh"

class A
{
int a = 1;
public int foo(A self)
{
return self.a;
}
};

class B : A
{
string b = "b";
public void __new(B self, int a, string b)
{
self.a = a;
self.b = b;
};
public int foo(B self)
{
return self.a * self.a;
}
};

class C : B
{
float c = 3.0f;
A inner_cls = nil;

public void __new(C self, int a, string b, float c)
{
B.__new(self, a, b);
self.c = c;
};

public int foo(C self)
{
return B.foo(self) + A.foo(self) + (int)self.c;
};

public int foo2(C self)
{
return self.inner_cls.foo();
};

public int static_foo(int a)
{
return a * a;
};
};

void _test_equal(mixed a, mixed b, string msg = nil)
{
printf("test_equal: %M\n", a);
test_equal(a, b, msg);
}

void _test_same(mixed a, mixed b, string msg = nil)
{
printf("test_same: %M\n", a);
test_same(a, b, msg);
}

void test_class()
{
_test_equal(A.a, 1);
A a = A.new();
_test_equal(a.a, 1);
_test_equal(a.get_class(), A);
_test_equal(B.b, "b");
B b = B.new(2, "bb");
_test_equal(b.get_class(), B);
_test_equal(b.a, 2);
_test_equal(b.b, "bb");
_test_same(b.map_data(), {"b" : "bb", "a" : 2});
C c = C.new(3, "bb", 12);
_test_same(c.foo(), 24);
_test_same(C.static_foo(10), 100);
c.inner_cls = b;
_test_same(c.foo2(), 4);
}

test_class();

底层实现

class_map是map的一个子类型,底层实现为sub_type为1的map类型,所有的class_map都有同一个sub_type,这样实现的主要原因是我们对于子类型的判断比较严格,而对于class的类型判断,确实需要考虑继承关系的,无法简单得用底层sub_type的方式来处理

整体的实现上,clas_map就是常量map的语法糖,比如这样的一段代码:

class A 
{
int a = 1;
};

class B : B
{
string b = "bb";
};

基本等价于这一段代码:

const map A = {
"__class__" : "A",
"a" : 1,
};

const map B = A + {
"__class__" : "B",
"__inherits__" : ["A"],
"b" : "bb",
};

当然并不完全等价,我们还在编译时保存了类的全局信息,类名对应哪个常量map,类中有哪些字段,分别是什么类型等等,跟过细节可以看cmm_map_class.h/cmm_map_class.cpp的实现

带成员函数的情况也则是使用了新的常量函数的实现,如这样的一个类:

test_class_map2.gs
class A
{
int a = 2;
public void __new(A self, int a)
{
self.a = a;
};
public void foo(A self, int a)
{
printf("foo = %d\n", a + self.a);
};
}
printf("A = %O\n", A);
A a = A.new(3);
printf("A.new(3) = %O\n", a);
printf("program functions = %O\n", this.get_program().get_functions());
a.foo(10);

打印出来会看到:

A = { /* sizeof() == 5 */
"a" : 2,
"__new" : (: .__new@A :)<no this>,
"foo" : (: .foo@A :)<no this>,
"__class__" : "A",
"__inherits__" : [ ],
}
A.new(3) = class A{ /* sizeof() == 1 */
"a" : 3,
----- class members -----
"a" : 2,
"__new" : (: .__new@A :)<no this>,
"foo" : (: .foo@A :)<no this>,
"__class__" : "A",
"__inherits__" : [ ],
}
program functions = [ /* sizeof() == 3 */
"::entry",
"__new@A",
"foo@A",
]
foo = 13
  • (: .foo@A :)就是这个对象上的一个函数,no_this说明没有绑定对象实例
  • 因为我们要把这个函数放在一个常量map中,常量在编译时生成,肯定还不能有对应的对象示例,也不应该被绑定在某个域,为了支持常量函数,我们支持了让某个函数不绑定具体的对象实例,而是可以在调用的时候指定,可以看下remove_ob_instance和call_by_instance的说明
  • 看a.foo(10),实例调用函数的时候,会把自己作为第一个参数传入
  • 打印new出来的class_map实例时,会显示class的members,方便调试
  • 为什么是foo@A,而不是foo?因为我们可以在一个文件内定义多个类,每个类都可以有个foo方法,比如运行这段代码:
class A { public void foo() {} };
class B { public void foo() {} };
class C { public void foo() {} };
printf("program functions = %O\n",
this.get_program().get_functions());

就能看到,结果是有三个foo函数:

program functions = [ /* sizeof() == 4 */
"::entry",
"foo@A",
"foo@B",
"foo@C",
]

内置方法

1. 给定程序名称和类名,寻找对应program中的parent-class常量

函数原型:
class_map? class_map.find_class_define(string program_name, string class_name)
使用方法:
//

2. 获取指定class实例对应的parent-class常量

函数原型:
class_map class_map.get_class(class_map cls)
使用方法:
//

3. 获取指定class实例对应的parent-class常量

函数原型:
bool class_map.is_a(class_map cls_instance, class_map cls)
使用方法:
//

4. 将class数据转换成普通的map,包含class的所有非内部字段的值

函数原型:
map class_map.map_data(class_map cls)
使用方法:
//

5. 获取给定class实例或常量的所有方法

函数原型:
map class_map.methods(class_map cls)
使用方法:
//

6. 获取给定class实例或常量的所有字段数据,包括名称、类型和初始值

函数原型:
map class_map.fields(class_map cls)
使用方法:
//

7. 手动调用给定class实例的析构方法(如果有的话)

函数原型:
void class_map.close(map cls_instance)
使用方法:
//