标签 运行时类型识别 下的文章

在上一节中我们手工实现了虚函数表,完成了继承、重写以及向上转换的操作,接下来我们要实现向下转换,它的实现大概是下面的样子:

pub fn dynamic_cast<'a, B, D>(base: &'a B) -> Option<&'a D>
{
    if can_dynamic_cast_to::<B, D>(base)
    {
        Some(unsafe { reinterpret_cast(base) })
    }
    else
    {
        None
    }
}

这里我们使用了上一节实现的 reinterpret_cast 函数来进行类型的转换,实际上 reinterpret_cast 可以转换任何类型,而不管转换是否安全,因此才会被标记为 unsafe,上一节我们用它做向上转换,是因为我们可以确保转换是安全的,但在这里就要先行检测安全再进行转换。
为了实现 can_dynamic_cast_to 函数,我们需要一些额外的信息,也就是运行时类型识别,我们先简单实现一个:

pub struct TypeInfo
{
    base_class: Option<&'static TypeInfo>
}
impl TypeInfo
{
    fn is_same(&self, other: &TypeInfo) -> bool
    {
        self as *const TypeInfo == other as *const TypeInfo
    }
    fn is_base_of(&self, other: &TypeInfo) -> bool
    {
        let mut ret = self.is_same(other);
        if !ret
        {
            if let Some(other) = other.base_class
            {
                ret = self.is_base_of(other);
            }
        }
        ret
    }
}

这个类型信息简单了点,可以从当前类一直向上查找到基类为止,别看它小,但用于我们实现 can_dynamic_cast_to 足够了。
接下来要为每个类添加类型信息,如下:

pub struct BaseVTable
{
    _type_info_: &'static TypeInfo,
    ...
}
impl Base
{
    pub const TYPEINFO: TypeInfo = TypeInfo
    {
        base_class: None,
    };
    pub const VTABLE: BaseVTable = BaseVTable
    {
        _type_info_: &Self::TYPEINFO,
        ...
    };
}
pub struct Derive1VTable
{
    _type_info_: &'static TypeInfo,
    ...
}
impl Derive1
{
    pub const TYPEINFO: TypeInfo = TypeInfo
    {
        base_class: Some(&Base::TYPEINFO),
    };
    pub const VTABLE: Derive1VTable = Derive1VTable
    {
        _type_info_: &Self::TYPEINFO,
        ...
    };
}
pub struct Derive2VTable
{
    _type_info_: &'static TypeInfo,
    ...
}
impl Derive2
{
    pub const TYPEINFO: TypeInfo = TypeInfo
    {
        base_class: Some(&Derive1::TYPEINFO),
    };
    pub const VTABLE: Derive2VTable = Derive2VTable
    {
        _type_info_: &Self::TYPEINFO,
        ...
    };
}

现在我们可以实现 can_dynamic_cast_to 了,如下:

fn can_dynamic_cast_to<B, D>(base: &B) -> bool
{
    let typeinfo: &TypeInfo =
    {
        let p = base as *const B;
        let p = p as *const *const *const TypeInfo;
        unsafe { &***p }
    };
    &D::TYPEINFO.is_base_of(typeinfo)
}

根据我们实现的类的内存布局,无论是任何类,第一级指针,指向对象本身,这是毋庸置疑的;因为 vptr 是类的第一个成员,所以第二级指针都指向虚表;而虚表中第一个元素是指向类型信息的指针,所以第三级指针指向类型信息。因此我们将对象的引用转换为三级的类型信息指针,从而获取到对象的实际类型信息。
接下来我们通过 D::TYPEINFO 来获取要转换到的类型的类型信息,从而判断是否可以进行转换。但是这里存在两个问题:

  1. 如果用户传递给我们的 base 参数不是一个我们实现的类,甚至是一个 i32 或者 &str,怎么办?我们知道这样做是不合法的,却无法阻止这样的事情发生,甚至我们连安全性都不能保证。如下:

    if let Some(_) = dynamic_cast<i32, i64>(22) ...
    if let Some(_) = dynamic_cast<&str, Derive2>("abcd") ...
  2. D::TYPEINFO 无法通过编译,Rust 的模板要求在展开前进行语法检查,此刻 Rust 还不知道 D 的定义,这和 C++ 的模板不一样。

幸运的是,两个问题可以用一个方法解决,我们可以定义一个 trait TypeInfoTrait,并且我们的类都要求实现 TypeInfoTrait,如下:

pub unsafe trait TypeInfoTrait
{
    fn get_typeinfo() -> &'static TypeInfo;
}
unsafe impl TypeInfoTrait for Base
{
    fn get_typeinfo() -> &'static TypeInfo { &Self::TYPEINFO }
}
unsafe impl TypeInfoTrait for Derive1
{
    fn get_typeinfo() -> &'static TypeInfo { &Self::TYPEINFO }
}
unsafe impl TypeInfoTrait for Derive2
{
    fn get_typeinfo() -> &'static TypeInfo { &Self::TYPEINFO }
}

如此一来,我们可以要求模板参数 B、D 都实现 TypeInfoTrait,缩小了 dynamic_cast 方法的适用范围,在一定程度上保障了安全,但我们无法阻止用户自行实现 TypeInfoTrait,所以我们将它标记为 unsafe。

fn can_dynamic_cast_to<B, D>(base: &B) -> bool
where
    B: TypeInfoTrait,
    D: TypeInfoTrait,
{
    let typeinfo: &TypeInfo =
    {
        let p = base as *const B;
        let p = p as *const *const *const TypeInfo;
        unsafe { &***p }
    };
    &D::get_typeinfo().is_base_of(typeinfo)
}
pub fn dynamic_cast<'a, B, D>(base: &'a B) -> Option<&'a D>
where
    B: TypeInfoTrait,
    D: TypeInfoTrait,
...
pub fn dynamic_cast_mut<'a, B, D>(base: &'a mut B) -> Option<&'a mut D>
where
    B: TypeInfoTrait,
    D: TypeInfoTrait,
...

dynamic_cast 实现完成,我们来验证一下:

use crate::dynamic_cast;
fn func3(base: &super::Base) -> (i32, i32, i32)
{
    let z = if let Some(d1) = dynamic_cast::<super::Base, super::Derive1>(base)
    { d1.func3() } else { -1 };
    (base.func1(), base.func2(100), z)
}
#[test]
fn test_fn2()
{
    let b = super::Base::new(1, 2);
    assert_eq!((1, 102, -1), func3(&b));
    let d1 = super::Derive1::new(1, 2, 3);
    assert_eq!((3, 102, 3), func3(&d1));
    let d2 = super::Derive2::new(1, 2, 3);
    assert_eq!((3, 302, 203), func3(&d2));
}

自此,我们手工实现 C++ 类的功能已经验证完成,接下来我们要开始用宏来生成这些代码。