18 KiB
06 - 面向对象与运算符重载
Viper 中的 class 有两种语义:数据布局(结构体/联合体/枚举,见 05-classes.md)和面向对象。面向对象特性通过 @t.Object 装饰器启用,多态通过 @t.CVTable 装饰器启用,非多态继承(C++ 风格,无 vtable 开销)通过 @t.NoVTable 装饰器启用。
@t.Object 面向对象
使用 @t.Object 装饰器启用面向对象特性,类将拥有成员方法、运算符重载、属性装饰器等 OOP 支持。编译器也会自动识别:如果结构体内有函数定义,会自动使用 @t.Object 行为,但在实际工程中更推荐明确标记,避免未定义行为。
基本定义
@t.Object
class ViperKernel:
BootInfo: bootinfo | t.CPtr
VGA: vga._VGAScreenDriver | t.CPtr
VGA_drv: vga._VGAScreenDriver
def __init__(self):
self.BootInfo = BootInfo
serial.init()
serial.puts("ViperOS VKernel Test\n")
避免在 __init__ 中使用 return,虽然不会报错,但是你永远无法获得 return 的结果,如果需要提前结束代码流,return 也不失为一种办法。
成员方法
@t.Object
class Sheet:
id: t.CInt
x: t.CInt
y: t.CInt
w: t.CInt
h: t.CInt
def __init__(self, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt):
self.x = x
self.y = y
self.w = w
self.h = h
def MoveTo(self, nx: t.CInt, ny: t.CInt):
self.x = nx
self.y = ny
def Resize(self, nw: t.CInt, nh: t.CInt):
self.w = nw
self.h = nh
方法调用
sheet: Sheet = Sheet(0, 0, 640, 480)
sheet.MoveTo(100, 100)
sheet.Resize(800, 600)
属性装饰器
支持 Python 的 @property 和 @xxx.setter 装饰器:
@xxx.setter和自定义装饰器正在试验阶段,尽可能不要使用
@t.Object
class MyObj:
_value: t.CInt
@property
def value(self) -> t.CInt:
return self._value
@value.setter
def value(self, v: t.CInt):
self._value = v
静态方法
@staticmethod
def _yield():
pass
对象指针与解引用
@t.Object 的实例通常通过指针操作:
obj_ptr: MyClass | t.CPtr = c.Addr(MyClass())
obj: MyClass = c.Deref(obj_ptr)
成员访问
通过 self 访问成员变量和方法:
def method(self) -> t.CVoid:
self.x = 10
y: t.CInt = self.y
self.DoSomething()
内嵌对象
@t.Object 可以包含其他对象实例(非指针):
@t.Object
class Sheet:
_surf_obj: gfx.Surface
_renderer_obj: gfx.Renderer
surf: gfx.Surface | t.CPtr
renderer: gfx.Renderer | t.CPtr
def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: t.CInt, h: t.CInt):
self._surf_obj = gfx.Surface(fb, zb, w, h)
self.surf = c.Addr(self._surf_obj)
self._renderer_obj = gfx.Renderer(self.surf)
self.renderer = c.Addr(self._renderer_obj)
__before_init__ 方法
所有类(不限 @t.Object/@t.CVTable)自动生成 __before_init__ 方法,在 __init__ 之前自动填充成员默认值,并对 @t.CVTable 类初始化 vtable:
define void @"<SHA1>.ClassName.__before_init__"(%"<SHA1>.ClassName"* %self) {
entry:
; 对 @t.CVTable 类:存储 vtable 指针到 slot 0
; 遍历所有成员:若有默认值则 store 默认值,否则 store 零值
ret void
}
职责:
- 填充类定义中声明的成员默认值(如
x: t.CInt = 0中的0) - 对无默认值的成员填充类型零值
- 对
@t.CVTable类额外初始化 vtable 指针
调用时机(由编译器在构造时自动插入):
alloca栈分配后调用一次- 若存在
__new__方法且返回堆指针,对堆指针再调用一次(alloca 的默认值已随栈帧丢弃,必须重新填充) __init__调用前
就地构造:用户也可手动调用 __before_init__ 实现就地构造(placement new 语义),典型用法见 mpool.alloc_buf:
buf: Buf | t.CPtr = pool.alloc(Buf.__sizeof__())
buf.__before_init__() # 填充默认值
buf.__init__(args) # 用户初始化逻辑
跨模块导入类时,导入侧也会声明 __before_init__,保证跨模块构造行为一致。
@t.NoVTable 非多态继承
@t.NoVTable 启用 C++ 风格的非多态继承(对应 struct A : B {} 无 virtual)。与 @t.CVTable 的区别:
| 特性 | @t.NoVTable |
@t.CVTable |
|---|---|---|
| vtable 指针 | ❌ 无 | ✅ 自动插入 slot 0 |
| 虚派发 | ❌ 无 | ✅ 支持 |
| 方法继承 | ✅ 生成包装函数转发 | ✅ 通过 vtable |
| 字段展平 | ✅ 父类字段前置嵌入 | ✅ 父类字段前置嵌入(vtable 之后) |
| 运行时开销 | 零 | 每次虚调用一次间接跳转 |
基本定义
@t.NoVTable
class LinkedNode:
next: "LinkedNode" | t.CPtr
prev: "LinkedNode" | t.CPtr
def append(self, node: "LinkedNode" | t.CPtr): ...
@t.NoVTable
class GNode(LinkedNode): # 非多态继承
value: t.CInt
子类 GNode 的结构体布局为 {next, prev, value}(父类字段前置,无 vtable 指针)。
方法继承机制
子类调用父类方法时,编译器通过 _generate_inherited_method_wrappers 自动生成包装函数:将 self bitcast 为父类指针后尾调用父类方法。例如 gnode.append(child) 会生成:
define void @"<SHA1>.GNode.append"(%"<SHA1>.GNode"* %self, ...) {
entry:
%base = bitcast %"<SHA1>.GNode"* %self to %"<SHA1>.LinkedNode"*
call void @"<SHA1>.LinkedNode.append"(%"<SHA1>.LinkedNode"* %base, ...)
ret void
}
跨模块继承
跨模块父类(如 linkedlist.LinkedNode)的 @t.NoVTable 标记会在导入时被检测并记录,子类继承时同样跳过 vtable 插入。
向下转型
父类指针→子类指针需显式转型(生成 bitcast IR):
root: Node = Node() # Node 继承 LinkedNode
child: LinkedNode | t.CPtr = root.child
# child 是 LinkedNode*,需要转回 Node*:
node: Node | t.CPtr = (Node | t.CPtr)(child)
隐式转型仅向上(子→父)。
@t.CVTable 虚函数表与多态
尽可能避免使用
self[0]来获取结构体成员,其不稳定,也不要手动操作虚表。
使用 @t.CVTable 装饰器可以启用虚函数表支持。启用后,结构体第一个成员自动插入 i8* 类型的 vtable 指针。@t.CVTable 的操作必须配合 @t.Object,但有时编译器会自动识别是否存在结构体内函数,自动使用 @t.Object。
基本定义
@t.Object
@t.CVTable
class Base:
def __init__(self):
pass
def method(self) -> t.CInt:
return 0
在 LLVM IR 中,@t.CVTable 类的结构体类型自动插入 vtable 指针:
%"<SHA1>.Base" = type { i8*, i32 }
同时,编译器会生成全局 vtable 变量:
@"<SHA1>.Base_Vtable" = internal global [1 x i8*] zeroinitializer
继承与多态
虚表可以帮你使用继承语法实现多态:
@t.Object
@t.CVTable
class Base:
def __init__(self):
pass
def method(self) -> t.CInt:
return 0
@t.Object
@t.CVTable
class M1(Base):
def method(self) -> t.CInt:
return 1
当 M1 被初始化时,VTable 会自动修改,这点和 C++ 相同,尽可能避免直接操作 VTable。
继承时,子类会自动继承父类的成员变量和方法。子类重写父类方法时,vtable 中对应位置的函数指针被子类方法替换,实现多态。
VTable 运行时修改
如果需要临时构造一个额外的 Base 结构体,还可以使用:
@t.Object
@t.CVTable
class Base:
def __init__(self):
pass
def method(self) -> t.CInt:
return 0
def __method1(self) -> t.CInt:
return 1
def main() -> t.CInt | t.CExport:
b: Base = Base()
b.method = __method1
启用 VTable 的表中,允许直接修改内部函数为函数指针,在底层,此操作将修改 VTable 来达成目的。编译器会检测当前 vtable 是否与类 vtable 相同,如果相同则创建 vtable 的副本(memcpy),避免修改影响所有实例。
虚方法调度机制
虚方法调用的核心流程:
- 从对象指针 GEP 获取第 0 个 slot(vtable 指针)
- 加载 vtable 指针
- bitcast 为
[N x i8*]*类型 - GEP 获取方法索引处的函数指针
- 加载函数指针
- bitcast 为正确的函数指针类型
- 间接调用
继承
结构体成员继承
当子类继承父类(父类在 Gen.class_members 中存在)时:
- 父类的成员变量被继承到子类(排在子类成员之前)
- 父类的默认值也被继承
- 父类的方法被继承(重命名为
子类名.方法名)
@t.Object
class Animal:
name: list[t.CChar, 32]
age: t.CInt
def __init__(self, age: t.CInt):
self.age = age
def Speak(self) -> t.CVoid:
serial.puts("...\n")
@t.Object
class Dog(Animal):
breed: t.CInt
def Speak(self) -> t.CVoid:
serial.puts("Woof!\n")
异常类继承
Viper 支持异常类的继承,用于 try/except 的异常匹配:
class MyError(Exception):
pass
class SpecificError(MyError):
pass
详见 09-exceptions.md。
运算符重载
@t.Object 类支持运算符重载。编译器在遇到运算符时,首先检查左操作数是否为结构体类型,如果是则尝试调用对应的 dunder 方法。
算术运算符重载
| 运算符 | Dunder 方法 | 示例 |
|---|---|---|
+ |
__add__ |
a + b → a.__add__(b) |
- |
__sub__ |
a - b → a.__sub__(b) |
* |
__mul__ |
a * b → a.__mul__(b) |
/ |
__truediv__ |
a / b → a.__truediv__(b) |
// |
__floordiv__ |
a // b → a.__floordiv__(b) |
% |
__mod__ |
a % b → a.__mod__(b) |
** |
__pow__ |
a ** b → a.__pow__(b) |
示例:
@t.Object
class Vec2:
x: t.CFloat
y: t.CFloat
def __init__(self, x: t.CFloat, y: t.CFloat):
self.x = x
self.y = y
def __add__(self, other: Vec2) -> Vec2:
result: Vec2 = Vec2(0.0, 0.0)
result.x = self.x + other.x
result.y = self.y + other.y
return result
def __mul__(self, scalar: t.CFloat) -> Vec2:
result: Vec2 = Vec2(0.0, 0.0)
result.x = self.x * scalar
result.y = self.y * scalar
return result
增量赋值运算符重载
| 增量运算符 | 优先 Dunder | 回退 Dunder |
|---|---|---|
+= |
__iadd__ |
__add__ |
-= |
__isub__ |
__sub__ |
*= |
__imul__ |
__mul__ |
/= |
__itruediv__ |
__truediv__ |
//= |
__ifloordiv__ |
__floordiv__ |
%= |
__imod__ |
__mod__ |
**= |
__ipow__ |
__pow__ |
如果未定义增量版本(如 __iadd__),编译器自动回退到普通版本(如 __add__)。
比较运算符重载
实验性:以下比较运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。定义它们不会报错,但比较运算不会自动调用。
| 运算符 | Dunder 方法 | 状态 |
|---|---|---|
== |
__eq__ |
仅类型推断识别 |
!= |
__ne__ |
仅类型推断识别 |
< |
__lt__ |
仅类型推断识别 |
<= |
__le__ |
仅类型推断识别 |
> |
__gt__ |
仅类型推断识别 |
>= |
__ge__ |
仅类型推断识别 |
反向运算符重载
实验性:以下反向运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。
| Dunder 方法 | 预期语义 | 状态 |
|---|---|---|
__radd__ |
右操作数加法 | 仅类型推断识别 |
__rsub__ |
右操作数减法 | 仅类型推断识别 |
__rmul__ |
右操作数乘法 | 仅类型推断识别 |
__rtruediv__ |
右操作数真除法 | 仅类型推断识别 |
__rfloordiv__ |
右操作数整除 | 仅类型推断识别 |
__rmod__ |
右操作数取模 | 仅类型推断识别 |
__rpow__ |
右操作数幂运算 | 仅类型推断识别 |
一元运算符重载
实验性:以下一元运算符 dunder 方法在编译器中被识别,但尚未实现自动调度。
| 运算符 | Dunder 方法 | 状态 |
|---|---|---|
-a |
__neg__ |
仅类型推断识别 |
+a |
__pos__ |
仅类型推断识别 |
可调用对象 __call__
@t.Object
class Counter:
value: t.CInt
def __init__(self):
self.value = 0
def __call__(self) -> t.CInt:
self.value += 1
return self.value
c: Counter = Counter()
x: t.CInt = c() # x = 1
y: t.CInt = c() # y = 2
下标访问重载
__getitem__ — 读取
def __getitem__(self, key: t.CInt) -> t.CInt:
return self.data[key]
__setitem__ — 赋值
def __setitem__(self, key: t.CInt, value: t.CInt):
self.data[key] = value
__delitem__ — 删除
def __delitem__(self, key: t.CInt):
pass
布尔转换 __bool__
@t.Object
class Buffer:
size: t.CInt
data: list[t.CChar, 1024]
def __bool__(self) -> t.CBool:
return self.size > 0
buf: Buffer = Buffer()
if buf:
serial.puts("buffer is not empty\n")
长度 __len__
@t.Object
class Buffer:
size: t.CInt
def __len__(self) -> t.CInt:
return self.size
buf: Buffer = Buffer()
n: t.CInt = len(buf)
字符串转换 __str__
@t.Object
class Point:
x: t.CInt
y: t.CInt
def __str__(self) -> t.CConst | t.CChar | t.CPtr:
return "Point"
p: Point = Point(1, 2)
print(p) # 调用 p.__str__()
绝对值 __abs__
@t.Object
class SignedValue:
val: t.CInt
def __abs__(self) -> t.CInt:
if self.val < 0:
return -self.val
return self.val
sv: SignedValue = SignedValue()
sv.val = -42
a: t.CInt = abs(sv) # a = 42
迭代器协议
Viper 的迭代器协议与 Python 相同,但 __next__ 的实现方式通过 StopIteration 异常来结束。
__iter__ 和 __next__
@t.Object
class IntRange:
start: t.CInt
end: t.CInt
current: t.CInt
def __init__(self, start: t.CInt, end: t.CInt):
self.start = start
self.end = end
self.current = start
def __iter__(self):
return self
def __next__(self, stop_flag) -> t.CInt:
if self.current >= self.end:
raise StopIteration
val: t.CInt = self.current
self.current += 1
return val
for 循环使用
r: IntRange = IntRange(0, 10)
for i in r:
print(i)
编译器生成的等价逻辑:
IntRange r = IntRange_init(0, 10);
IntRange iter = IntRange___iter__(&r);
while (1) {
i1 stop_flag = 0;
int i = IntRange___next__(&iter, &stop_flag);
if (stop_flag == 1) break;
print_int(i);
}
上下文管理器
with 语句要求对象实现 __enter__ 和 __exit__ 方法:
@t.Object
class File:
fd: t.CInt
path: t.CConst | t.CChar | t.CPtr
def __init__(self, path: t.CConst | t.CChar | t.CPtr, flags: t.CInt):
self.path = path
self.fd = -1
def __enter__(self):
self.fd = fat32.open(self.path, self.flags)
return self
def __exit__(self):
fat32.close(self.fd)
def read(self) -> t.CInt:
return fat32.read(self.fd)
使用方式:
with File("/test.txt", FA_READ) as f:
data: t.CInt = f.read()
编译器会自动保证 __exit__ 在 with 块结束时被调用。
析构 __del__ / __delete__
del obj # 调用 obj.__del__() 或 obj.__delete__()
del arr[idx] # 调用 arr.__delitem__(idx)
Dunder 方法完整参考
已实现自动调度的 Dunder 方法
| Dunder 方法 | 触发语法 | 说明 |
|---|---|---|
__init__ |
ClassName(args) |
构造函数 |
__before_init__ |
自动生成 | vtable 初始化,在 __init__ 之前执行 |
__add__ |
a + b |
加法 |
__sub__ |
a - b |
减法 |
__mul__ |
a * b |
乘法 |
__truediv__ |
a / b |
真除法 |
__floordiv__ |
a // b |
整除 |
__mod__ |
a % b |
取模 |
__pow__ |
a ** b |
幂运算 |
__iadd__ |
a += b |
增量加法(回退到 __add__) |
__isub__ |
a -= b |
增量减法(回退到 __sub__) |
__imul__ |
a *= b |
增量乘法(回退到 __mul__) |
__itruediv__ |
a /= b |
增量真除法(回退到 __truediv__) |
__ifloordiv__ |
a //= b |
增量整除(回退到 __floordiv__) |
__imod__ |
a %= b |
增量取模(回退到 __mod__) |
__ipow__ |
a **= b |
增量幂运算(回退到 __pow__) |
__call__ |
obj(args) |
可调用对象 |
__getitem__ |
obj[key] |
下标读取 |
__setitem__ |
obj[key] = value |
下标赋值 |
__delitem__ |
del obj[key] |
下标删除 |
__bool__ |
if obj: |
布尔转换 |
__len__ |
len(obj) |
长度 |
__str__ |
str(obj) / print(obj) |
字符串转换 |
__abs__ |
abs(obj) |
绝对值 |
__iter__ |
for x in obj: |
获取迭代器 |
__next__ |
for x in obj: |
获取下一个值(标志位方式) |
__enter__ |
with obj as x: |
上下文管理器进入 |
__exit__ |
with obj as x: |
上下文管理器退出 |
__del__ |
del obj |
析构 |
__delete__ |
del obj |
删除描述符 |
声明支持但未实现自动调度的 Dunder 方法
| Dunder 方法 | 预期触发语法 | 当前状态 |
|---|---|---|
__eq__ |
a == b |
仅类型推断识别,比较运算不自动调度 |
__ne__ |
a != b |
同上 |
__lt__ |
a < b |
同上 |
__le__ |
a <= b |
同上 |
__gt__ |
a > b |
同上 |
__ge__ |
a >= b |
同上 |
__neg__ |
-a |
仅类型推断识别,一元负号不自动调度 |
__pos__ |
+a |
同上 |
__radd__ |
右操作数加法 | 仅类型推断识别 |
__rsub__ |
右操作数减法 | 同上 |
__rmul__ |
右操作数乘法 | 同上 |
__rtruediv__ |
右操作数真除法 | 同上 |
__rfloordiv__ |
右操作数整除 | 同上 |
__rmod__ |
右操作数取模 | 同上 |
__rpow__ |
右操作数幂运算 | 同上 |