在 C++ 中我们可以用基类的指针来持有派生来对象,如下:

Base* p = new Derive1(1, 2, 3);

但在 Rust 中,原始指针的使用是受限的,我们需要用智能指针。下面我们来尝试一下:

let b: Box<Base> = Box::<Derive1>::new(Derive1::new(1, 2, 3));
let b: Box<Base> = Box::<Base>::new(Derive1::new(1, 2, 3));

并不顺利,编译器认为 Box<Base> 和 Box<Derive1> 以及 Base 和 Derive1 是不同的类型,因而拒绝赋值。

   --> class_impl/src/lib.rs:392:24
    |
392 |     let b: Box<Base> = Box::<Derive1>::new(Derive1::new(1, 2, 3));
    |            ---------   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Base`, found struct `Derive1`
    |            |
    |            expected due to this
error[E0308]: mismatched types
    = note: expected struct `Box<Base>`
               found struct `Box<Derive1>`
   --> class_impl/src/lib.rs:393:41
393 |     let b: Box<Base> = Box::<Base>::new(Derive1::new(1, 2, 3));
    |                                         ^^^^^^^^^^^^^^^^^^^^^ expected struct `Base`, found struct `Derive1`

我们再尝试一下 dyn 关键字:

let b: Box<dyn Base> = Box::<Derive1>::new(Derive1::new(1, 2, 3));
let b: Box<dyn Base> = Box::<dyn Base>::new(Derive1::new(1, 2, 3));

仍然被拒绝。

error[E0404]: expected trait, found struct `Base`
   --> class_impl/src/lib.rs:392:20
392 |     let b: Box<dyn Base> = Box::<Derive1>::new(Derive1::new(1, 2, 3));
    |                    ^^^^ not a trait
error[E0404]: expected trait, found struct `Base`
   --> class_impl/src/lib.rs:393:20
393 |     let b: Box<dyn Base> = Box::<dyn Base>::new(Derive1::new(1, 2, 3));
    |                    ^^^^ not a trait
error[E0404]: expected trait, found struct `Base`
   --> class_impl/src/lib.rs:393:38
393 |     let b: Box<dyn Base> = Box::<dyn Base>::new(Derive1::new(1, 2, 3));
    |                                      ^^^^ not a trait

既然编译器不支持,我们只好自己动手了。

    let d = Box::new(Derive1::new(1, 2, 3));
    let p = Box::into_raw(d) as *mut Base;
    let b = unsafe { Box::<Base>::from_raw(p) };

这样虽然可以达到目的,但太过于繁琐了,而且还使用了不安全的代码。我们将它写成一个安全的函数:

fn make_box<B, D>(d: D) -> Box<B>
{
    let d = Box::new(d);
    let p = Box::into_raw(d) as *mut B;
    unsafe { Box::<B>::from_raw(p) }
}
let b: Box<Base> = make_box(Derive1::new(1, 2, 3));

如此一来,确实方便了,但同时也带来了安全隐患,我们没有验证类型是否可以转换,也就是说,我们无法阻止用户非法使用 make_box 方法,如下:

let d: Box<Derive1> = make_box(Base::new(1, 2));
let p: Box<i32> = make_box(Vec::<String>::new());

为此我们需要改进 make_box 函数,如下:

fn make_box<B, D>(d: D) -> Box::<B>
where
    D: DerefMut<Target=B>,
{
    let p = Box::new(d);
    let d = unsafe { &mut *Box::into_raw(p) };
    let b = d as &mut B;
    unsafe { Box::<B>::from_raw(b as *mut B) }
}
let b: Box<Base> = make_box(Derive1::new(1, 2, 3));

在这里,我们先将指针转换为引用,虽然用了不安全的代码,但我们知道在这里是安全的,然后通过引用来做类型转换,而这个转换会自动调用 DerefMut::deref_mut(&mut self) 方法,实现安全的类型转换。从而确保整个函数的行为是安全的,最后再将引用转换为指针及智能指针。虽然绕了好大一圈,代码也不少,但只要编译器足够聪明,仍然是 0 开销的。
这回我们不用担心非法的调用了,但新的问题又出现了。

let b: Box<Base> = make_box(Derive2::new(1, 2, 3));

因为 Base 不是 Derive2 的直接基类,再常规的代码中,因为 Derive2 as Derive1 as Base 这样的路径存在,我们可以直接 Dervie2 as Base,但是在 Rust 模板中,这样行不通,除非我们修改函数原型,如下:

fn make_box<B, D, D1>(d: D) -> Box::<B>
where
    D: DerefMut<Target=D1>,
    D1: DerefMut<Target=B>,

这样就能够支持孙类指针赋值给基类,但又不支持子类的指针。
到这里,想要一个十全十美的方案似乎不太可能了。下面有几个不完美的方案
方案一,改用 AsMut:

fn make_box<B, D>(d: D) -> Box::<B>
where
    D: AsMut<B>,
{
    let p = Box::new(d);
    let d = unsafe { &mut *Box::into_raw(p) };
    let b = d.as_mut();
    unsafe { Box::<B>::from_raw(b as *mut B) }
}
impl AsMut<Base> for Base
{ fn as_mut(&mut self) -> &mut Base { self } }
impl AsMut<Base> for Derive1
{ fn as_mut(&mut self) -> &mut Base { unsafe { reinterpret_cast(self) } } }
impl AsMut<Derive1> for Derive1
{ fn as_mut(&mut self) -> &mut Derive1 { self } }
impl AsMut<Base> for Derive2
{ fn as_mut(&mut self) -> &mut Base { unsafe { reinterpret_cast(self) } } }
impl AsMut<Derive1> for Derive2
{ fn as_mut(&mut self) -> &mut Derive1 { unsafe { reinterpret_cast(self) } } }
impl AsMut<Derive2> for Derive2
{ fn as_mut(&mut self) -> &mut Derive2 { self } }

代价就是要为每一个基类及自身都实现 AsMut,随着类的继承层次增加,要实现的 AsMut 也会越来越多。
方案二,改用宏:

macro_rules! make_box
{
    ($expr:expr) => { Box::new($expr) };
    ($name:ident $expr:expr) =>
    {
        {
            let p = Box::new($expr);
            let d = unsafe { &mut *Box::into_raw(p) };
            let b = d as &mut $name;
            unsafe { Box::<$name>::from_raw(b as *mut $name) }
        }
    };
}
let mut v = Vec::<Box<Base>>::new();
v.push(make_box!(Base::new(1, 2)));
v.push(make_box!(Base Derive1::new(1, 2, 3)));
v.push(make_box!(Base Derive2::new(1, 2, 3)));

宏会在调用处展开,没有了模板参数的约束条件,因此无论是 Derive1 还是 Derive2 都可以直接 as Base,但不影响安全性,因为非法的 as 编译器会拒绝。
方案三,通过 typeinfo 验证:

fn make_box<B, D>(d: D) -> Box::<B>
where
    B: TypeInfoTrait,
    D: TypeInfoTrait,
{
    if B::get_typeinfo().is_base_of(D::get_typeinfo())
    {
        let b = Box::new(d);
        let p = Box::into_raw(b) as *mut B;
        unsafe { Box::<B>::from_raw(p) }
    }
    else
    {
        panic!("invalid cast.");
    }
}

通过 typeinfo 可以更准确的判断两个类型的指针是否可以赋值,因此不需要将指针转换为引用再做 as 的操作来验证,但缺点是,判断发生于运行时,而不是编译时,而且每次转换都有了代价。
我们总算是解决了派生类指针赋值给基类指针的问题,下面开始验证析构函数:

impl Drop for Base
{
    fn drop(&mut self){ println!("Base::drop."); }
}
impl Drop for Derive1
{
    fn drop(&mut self){ println!("Derive1::drop."); }
}
impl Drop for Derive2
{
    fn drop(&mut self){ println!("Derive2::drop."); }
}
let b: Box<Base> = make_box(Base::new(1, 2));
let b1: Box<Base> = make_box(Derive1::new(1, 2, 3));
let b2: Box<Base> = make_box(Derive2::new(1, 2, 3));

然而派生类的析构函数并没有被调用:

Base::drop.
Base::drop.
Base::drop.

这并不难理解,在 C++ 中要正确的调用派生类的析构函数也要走虚析构函数的。看来需要我们自己来实现智能指针和虚析构函数了,作为一个 C++ 程序员,这个不难。

标签: Rust 模拟 C++

已有 6 条评论

  1. 新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!

  2. 2025年10月新盘 做第一批吃螃蟹的人

  3. 手机出租在线解码+15561262350+http://baidu.com

  4. 2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
    新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
    新车首发,新的一年,只带想赚米的人coinsrore.com
    新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
    做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
    新车上路,只带前10个人coinsrore.com
    新盘首开 新盘首开 征召客户!!!coinsrore.com
    新项目准备上线,寻找志同道合的合作伙伴coinsrore.com
    新车即将上线 真正的项目,期待你的参与coinsrore.com
    新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
    新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com

  5. 2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
    新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
    新车首发,新的一年,只带想赚米的人coinsrore.com
    新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
    做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
    新车上路,只带前10个人coinsrore.com
    新盘首开 新盘首开 征召客户!!!coinsrore.com
    新项目准备上线,寻找志同道合的合作伙伴coinsrore.com
    新车即将上线 真正的项目,期待你的参与coinsrore.com
    新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
    新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com

  6. 2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
    新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
    新车首发,新的一年,只带想赚米的人coinsrore.com
    新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
    做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
    新车上路,只带前10个人coinsrore.com
    新盘首开 新盘首开 征召客户!!!coinsrore.com
    新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
    新车即将上线 真正的项目,期待你的参与coinsrore.com
    新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
    新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com

添加新评论