Vala语言的类型系统较为多样,可能会让初学者感到困惑,但其实部分内容只是为了减小性能开销,表现上并没有太多区别(对于初学者只用struct
和继承自GLib.Object
的class
其实就行了)。笔者在此对Vala中的类型系统进行简单介绍,可以用来了解那些不常用的类在什么时候有用QwQ。
Vala中的类型
- 继承自
GLib.Object
的类 - 非对象类
- 紧凑类
- 不透明紧凑类
以及类似物:
- 结构体
struct
如上文所述,初学者没有必要全部掌握,只需要会使用继承自GLib.Object
的类以及结构体struct
即可,其他的则主要是从性能角度考虑,往往在功能上有所缺失,继承自GLib.Object
的类的特性最为丰富。
主要区别因素
class
与struct
- Vala显式区分
class
与struct
。struct
与class
在Vala中的本质区别是struct
在栈上调用,通过拷贝值进行传递,而class
在堆上调用,通过指针进行传递
- Vala显式区分
- 是否在GObject类型系统中注册
- 是否继承自
GLib.Object
基类
访问权限
Vala的访问权限修饰符与C#类似:
public
: 没有访问限制private
: 访问仅限该类(在没有访问修饰符时默认)protected
: 访问仅限该类及继承自该类的子类internal
: 访问仅限同一个程序集
在某些为了性能而舍弃了一定完备性的类型系统中,部分访问修饰符不能使用。
结构体(struct
)
结构体在Vala中通过值进行传递,Vala的结构体下可以拥有字段,也可以拥有方法、属性,但结构体的字段的访问控制默认且只能为公开(public
),如果设置为其他修饰符,编译器会报出警告,并忽略这里的修饰符。但是,结构体的属性与方法仍然不受此限制,可以设定其访问修饰符。
结构体struct
均不是GObject类型系统的一部分。
类(class
)
类在Vala中通过指针进行传递,且在Vala中有多个种类。笔者在此逐一介绍。
继承自GLib.Object
的类
一般使用的类型系统,支持Vala中类型系统的全部特性,但性能开销相对较大(一般情况下类创建/销毁并不是性能瓶颈,仍非常快)。定义时,必须要显式指明从GLib.Object
或者GLib.Object
的子类继承,即使是从GLib.Object
继承时class Foo : GLib.Object
的基类指定仍然不可省略。
非对象类
非对象类是不从GLib.Object
继承,但仍然在GObject类型系统中注册的类,性能开销较继承自GLib.Object
的类小不少。1非对象类仍能实现GObject类的大多数功能,但由于这样的类型并非GLib.Object
的子类,利用了GLib.Object
的特性均无法使用,因此非对象类不能使用GObject风格的构造,包括Object (...)
与construct
构造语段,以及Object.new (type)
的新建对象方式。
紧凑类与不透明紧凑类
紧凑类(包括不透明紧凑类)与struct
类似,不在GObject类型系统中注册,特性较为受限,但紧凑类传递时仍然传递指针,与struct
存在根本区别。
由于没有在类型系统中注册,紧凑类比非对象类的性能开销还要小很多(创建/销毁快约2.5倍),1是十分轻量的类型。另一方面,紧凑类因此也无法使用GObject提供的大量功能。紧凑类与不透明紧凑类的唯一区别在于,紧凑类中的所有字段的访问控制只能为public
或internal
,而不透明紧凑类中的字段访问控制只能为private
或internal
,这两种类型均不能混合私有与公开字段。
定义紧凑类的方法是在class Foo { ... }
前面加上[Compact]
修饰符,类似地,定义不透明紧凑类则需要加[Compact (opaque = true)]
修饰符。
笔者在此简要列出紧凑类的受限之处:
- 与非对象类一样,不能使用GObject风格的构造
- 不能实现接口
- 紧凑类之间可以继承,但继承的紧凑类不能拥有新的字段
- 默认不通过引用计数管理
- 默认情况下紧凑类只能有一个强引用,在生命周期结束后自动销毁,其他引用只能是无属引用
- 可以加入
[Immutable]
修饰符并用[CCode (copy_function = "foo_copy")]
修饰符指定复制函数,实现强引用时自动拷贝
- 可以加入
- 也可以手动设置用于引用计数的函数来恢复引用计数(使用
[CCode (ref_function = "foo_up", unref_function = "foo_down")]
)
- 默认情况下紧凑类只能有一个强引用,在生命周期结束后自动销毁,其他引用只能是无属引用
- 不能实现信号
- 不能用
is
检测类型,也不能正确获取类型信息 - 不能混合
public
与private
修饰的字段,不能使用protected
修饰的字段
由于诸多限制的存在,仅在语言绑定中或对性能有特殊需求的场景下才推荐使用紧凑类;由于紧凑类完全不依赖GObject对象系统,如果不希望程序链接到GObject库,也可以使用紧凑类取代依赖GObject中的类型。
归纳
特性 | 继承自GLib.Object 的类 |
非对象类 | 紧凑类 | 不透明紧凑类 | 结构体 |
---|---|---|---|---|---|
传递方式 | 指针引用 | 指针引用 | 指针引用 | 指针引用 | 值拷贝 |
字段访问修饰 | 不受限 | 不受限 | public 或internal |
private 或internal |
无效(均公开) |
是否依赖GObject类型系统 | ✔ | ✔ | ✘ | ✘ | ✘ |
GObject风格构建支持 | ✔ | ✘ | ✘ | ✘ | ✘ |
运行时类型检测与类型信息获取 | ✔ | ✔ | ✘ | ✘ | ✘ |
接口 | ✔ | ✔ | ✘ | ✘ | ✘ |
默认引用计数 | ✔ | ✔ | ✘ | ✘ | ✘ |
信号 | ✔ | ✔ | ✘ | ✘ | ✘ |
继承 | ✔ | ✔ | ●(不支持实现新字段) | ●(不支持实现新字段) | ✘ |
抽象/虚方法 | ✔ | ✔ | ✔ | ✔ | ✘ |
创建性能开销(越高开销越大)1 | 10 | 2.5 | 1 | 1 | 1 |
-
Vala’s Memory Management Explained: Creating and destroying compact classes is faster than non-compact classes (about 2.5 times faster than regular classes, and an order of magnitude faster than GObject-derived classes), though other operations are no different. That said, modern hardware can create and destroy millions of GObject-derived classes per thread per second, so it is advisable to make sure this is a performance bottleneck before trying to optimize it away. 关于结构体需要注意的是,结构体的传递方式是值拷贝,因此虽然创建性能开销小,但是传递性能开销大。 ↩ ↩2 ↩3