AngelScript高级概念:object handle与OOP

DinS          Written on 2018/4/16

我们继续探讨AngelScript的高级特性,确保你已经掌握了之前专题的内容,可以从《AngelScript框架介绍》开始。现在假设读者已经充分掌握了AS的知识。

object handle在AngelScript里是一个非常重要的概念,这个东西是一种类型,只存在于脚本端。官方的解释是这样的An object handle is a type that can hold a reference to an object,以及这样的In AngelScript an object handle is a reference counted pointer to an object。
个人的理解是object handle介于引用(reference)和智能指针(shared_ptr)之间。在脚本中是不存在指针的,但是有些重要的特性,比如引用传参和多态都涉及指针,因此AS的作者制作了object handle这么一个特殊的类型来处理。
说object handle是引用,因为其使用方式跟值一样,比如类都是通过.执行方法,也可以传来传去。说object handle是智能指针,是因为它有引用计数,只有引用计数为0时才释放真正的对象。
更直接一点,把object handle当作shared_ptr,只不过使用.而不是->执行方法即可。

所有基本类型都没有object handle,这是显而易见的,没有这个必要。通常只有涉及复杂的类的时候才有用handle的必要,下面我们以引用传参为例认识handle的基本使用方法。

一、引用传参

首先是注册string和array,建立AS执行环境,这些都略过了。直接看脚本:

大图点这里

以上脚本应该返回字符串”5″。
我们使用了@表示取对象的handle,看起来跟引用很像,于是在函数里递增的结果会保留下来。

运行看结果:

如果去掉@就变成了传值,这样递增结果不会保留,让我们试试看:

so far so good。没什么难以理解的,跟c++一致。

还可以做的更好,注意我们传递参数的时候显式使用了@,实际上这个不是必须的,AS可以推断出应该用handle还是对象本身,比如脚本改成这样。

大图点这里

运行结果仍然是5。这样一来跟引用的写法一模一样。
由于AS可以智能决定是handle还是object,所以如果写一个object@ obj_h = object;也是可以通过的,但是这样有些干扰理解。

二、长生命周期

以上展示的是handle中偏向引用的一方面,接下来展示偏向智能指针的一方面,看脚本:

大图点这里

这次我们在一个函数内定义一个数组,如果是值那么离开作用域后就会释放。然而我们使用了handle来指向该数组,然后返回了该handle,这样一来只要还有指向该数组的handle,数组就不会释放,可以安全的使用。
运行看返回结果:

依然是5。

从这个例子来看,handle是一个智能指针。
注:我们使用了auto来掩盖@符号。如果返回的是对象,那么auto会推断出对象。如果返回对象但是想用handle装载,则需要写auto@ handle。

三、多态

这个也是OOP中的核心。
之所以说handle可以多态,是因为基类handle可以装载派生类handle,这个跟指针的道理是一样的。当然为了演示handle与多态实际上涉及了AS的类继承知识,在这里一并讲解。(注:对于OOP不了解的读者可参见《我们为什么需要面向对象编程?》等文章)

AS里的类感觉上跟java更接近,因为基类在AS里是一个单独的类别,称为Interface,这个跟java是一模一样的。
AS里没有多重继承,但是一个类可以继承任意个interface,这个跟java也是一模一样的。
对于每一个成员变量或函数,前面都需要加private或者protected,如果不加默认public,跟java一模一样。
于是按照java的方式去理解AS的继承应该更加好一点。我估计作者是c++和java都会,c++用的多,但是认为java在处理类和继承上更好,所以在AS中把二者结合了。

所有成员函数默认都是virtual,因此不用特意声明。可以显式增加override以确保派生类覆盖了基类方法,比如void Method() override {}。
看下面的类:

大图点这里

抽象类是Animal,Dog继承Animal,除了Eat外还可以Bark。
这本身没有什么奇特的,接下来看看handle的作用。

大图点这里

注意,array里面可以装handle,于是PickAnimal全部在抽象类上进行操作。将基类装在容器里也是OOP必备操作。
我们调用Dog的构造函数后,AS隐式将其转化为抽象类handle,于是操作抽象类实际上就是操作派生类对象了,这是OOP的基本思路。
如果需要将抽象类转为派生类,使用AS提供的cast。

运行结果:

这个是符合预期的。于是handle可以很好地完成多态的任务。
只有一个派生类倒是用不着多态,这个例子本身仅仅展示如何在AS里完成多态,有过OOP经验的程序员一看就可以理解。

注:并非所有类都可以变成handle,不过AS提供的类以及脚本里声明的类都可以,基本上够用了。另外特别注意string并不能变成handle。严格来说只有引用类才可以有handle,值类不行。

上面特别提到了array可以装载handle,一般而言我们需要容器来状态基类然后进行操作,但是array只能装载同类对象,AS提供了一个非常强大的数据结构:dictionary。其可以装载任意基本类型和任意类型的handle。
接下来让我们看看这个dictionary,用好了跟python里的dictionary一样强大,见《AngelScript SDK: dictionary讲解》。