美文网首页收藏
22章 RTTI / 运行时类型信息

22章 RTTI / 运行时类型信息

作者: my_passion | 来源:发表于2022-06-25 23:13 被阅读0次

22.1 类层次导航: 恢复 "丢失的" 多态对象类型信息

1 dynamic 引入

多重继承&交叉转换 -> 恢复 "丢失的" 多态对象类型信息: user 用 多重基类1的 ptr 指向 多态对象时, 不知道其中是否含 多重基类2

(1) UI 机制: 在 UI 系统(GUI库+控制屏幕) 与 应用程序 间 `传递 小部件(widget)`

                传 Ival_box 对象          \
    应用程序 - - - - - - - - - - - - - - - - UI 系统: `无需了解 Ival_box 细节`
             \  有动作发生 时, 回传 对象 给  

    auto pb = dynamic_cast<Ival_box*>(pw)
    
    多重继承&交叉转换 
        多态对象的类型信息 "丢失"
            user 用 多重基类1的 ptr 指向 多态对象时, 不知道其中是否含 多重基类2 
        dynamic_cast 
            恢复 "丢失的" 多态对象类型信息       

2 dynamic_cast<T*>(p) 要点 & 应用

(1) 运行时检测 多态对象 是否含 预期(非多态/多态)类型

调 vf/nvf 却 不用 虚调用机制

(2) 多重继承(多重基类) & 交叉/向下 转换

1) 实现类 -> 接口类: pw 指向 1个 Ival_box 吗?

    auto pb = dynamic_cast<Ival_box*>(pw) // BBwindow* pw, pw 指向 1个 Ival_box 吗? 

2) 接口类 -> 实现类: 通过 对象 I/O 系统 传输 具体类型数据, dynamic_cast 解析 出 具体类型数据

目标类型 T 不必是 多态的: 可在 多态类型 中包含 具体类型

    Date* pd = dynamic_cast<Date*>(pio);
    
    接口类/Io_obj: public 基类       
    实现类/Date: public 基类 

3) 目标类型 为 void*: 求多态对象 起始地址

    // 基类对象地址未必是 多态对象起始地址 => pb2 未必 == pb
    void* pb2 = dynamic_cast<void*>(pb);
    // 1) 
    void event_handle(BBwindow* pw)
    {
        if ( auto pb = dynamic_cast<Ival_box*>(pw) ) 
        {
            // ...
            int x = pb->get_value(); // 使用 Ival_box
            // ...
        }
        else
        {
            // ... 糟糕! 处理意外情况 
        }
    }
    // 2)                                       
    Date* pd = dynamic_cast<Date*>(pio);

    class Io_obj  // `对象 I/O 系统` 的 基类 
    {
        virtual Io_obj* clone() = 0;
    };
    
    class Io_date: public Date, public Io_obj {};
    
    void f(Io_obj* pio)
    {
        Date* pd = dynamic_cast<Date*>(pio);
        // ...
    }
    // 3) 
    void g(Ival_box* pb)
    {
        void* pb2 = dynamic_cast<void*>(pb);
    }
        
        void g(Ival_box* pb)
    {
        void* pb2 = dynamic_cast<void*>(pb);
    }

(3) 是 运算符, 接受 2 个 运算对象: 类型 + ptr/ref

(4) return 
    
    指向该对象的 T* 指针, 到 T 最严格是 protected 继承
    nullptr, else
            
p 可 == nullptr

(5) dynamic_cast 要求 ptr/ref 指向 多态类型(否则 找不到 vptr/vtbl/RTTI_ptr -> 报错)

=> 简化了 dynamic_cast 的 实现: 类型信息 ptr 放在 多态类型 vtbl

    RTTI (Run-Time Type Information): 运行时类型信息  

(6) 用于 类型转换

dynamic_cast 转换到 protected 基类 结果为 空指针, 不能 转换到 private 基类

    3 种 类型转换                                                         
        [1] 向下转换    Base -> Derived     static_cast 不安全                                                     
        [2] 向上转换   
        [3] 交叉转换    

    ————————————————————————————————————————————————————————————————————————————————————————————————————————————
    dynamic_cast                
            
        在含 `虚基类 + 重复基类` 的 `类层次` 中 `转换` 
    ————————————————————————————————————————————————————————————————————————————————————————————————————————————                
                    compiler 是否了解  => compiler `编译时`            是否 `有 二义性` 
                        `完整类框架`   能否 `检测出二义性` 
    ————————————————————————————————————————————————————————————————————————————————————————————————————————————                        
        向上转换        是                   是                   1] 经过   重复(子)对象             => 有二义性
                                                                    pRa -> pC
                                                                
                                                                2] 不经过 重复(子)对象              => 无二义性 
                                                                    pRa -> pTr/pRe / pS 
    ————————————————————————————————————————————————————————————————————————————————————————————————————————————                                                            
        向下转换        否                   否                   1] 经过   重复(子)对象(Component)   => 有二义性
                                                                    在 Radio 对象上 pS -> pC
                                                                
                                                                2] 不经过 重复(子)对象              => 无二义性
                                                                    在 Transmitter/Receiver 对象上 pS -> pC
    ————————————————————————————————————————————————————————————————————————————————————————————————————————————                                    
    
    
    
    note:
    —————————————————————————————————————————————————————————————————————————————
    dynamic_cast                

        `普通基类 向下转换` 结果 `不会有二义性`, 只可能是 `唯一子对象 ( 或 转换失败 )`
    —————————————————————————————————————————————————————————————————————————————

3 设计角度: 恢复接口(实现类 -> 接口类), 即 dynamic_cast 询问对象 是否提供了指定 接口

对象 I/O 系统: 从 istream 解析出 objSmartPtr -> dynamic_cast<Shape*>(sp.get() ) 检查 internal ptr 若为 指定 接口类型指针, 则 转换/恢复为 接口类型指针

以 类型安全的方式 传输 任意对象, 本例给出了 "接收端" 的1个 "蓝本"

    void user()                                                     
    {                                                               
        // ... 打开文件, 将 istream ss 与 之关联                    
                                                                                             
        //                                                          
        unique_ptr<Io_obj> sp { get_obj(ss) };                       
                                                                    
        if( auto sp = dynamic_cast<Shape*>(sp.get() ) )              
        {                                                           
            sp->draw();                                             
            // ...
        }
        else { /* ... */ }
    }                                               
                                                        
    using Pf = Io_obj* (istream&);                      
    map<string, Pf> io_map; // 字符串 - (对象) Factory Method                     
                                                    
    // 假定 输入流 = 表示对象类型的 string + objData 
    Io_obj* get_obj(istream& is) 
    {
        string str = get_word(is);
        if(auto f = io_map[str] )
            return f(is);
    }

Io_obj(抽象基类1) 类层次 & Shape(抽象基类2) 类层次

version1: 具体 Shape 派生自 Io_obj

version2: 借助 抽象基类 (Io_obj) 将 another 类层次中具体类型(Circle)纳入到已有 类层次(Io_obj)`

    struct Io_circle: Circle, Io_obj
    {
        Io_circle(istream&);
        
        Io_circle* clone() const { return new Io_circle(*this); }
        
        static Io_obj* new_circle(istream& is) { return new Io_circle{is}; }
    };                  
                                                
    io_map["Io_circle"] = &Io_circle::new_circle;
    

version3: 模板形参 T 作基类, 模板实参用 具体类型

    template <class T>
    struct Io: T, Io_obj {/* ... */};
    
    using Io_circle = Io<Circle>;
    
    问题: 模板形参 T 作基类 => `不能为 内置类型`
    解决: 

version4: 模板形参 T 作 成员, 模板实参用 具体类型

    template <class T>
    struct Io: Io_obj 
    {
        T val;
        // ...
    };
    
    using Io_int = Io<int>; // 封装了 内置类型 
        
    // note: 该 `模板函数` 必须 `显示指定 模板参数`: 由 模板实参 无法推导出 模板参数 T   
    template <class T>
    T* get_val<T>(Io_obj* p)
    {          
        if (auto pp = dynamic_cast<Io<T>* >(p) )
            return &pp->val;
        return nullptr;
    }
    
    void user()
    {
        // ... 
        
        if ( auto sp = get_val<Shape>(p.get() )  )
        {                                                           
            sp->draw();                                             
            // ...
        }
    }
dynamic_cast 之 `交叉转换`.jpg

4 转换 引用类型

1) dynamic_cast<T*>(p) / dynamic_cast<T&>(r)

[1] 是 询问 / 断言

    p/r 所指对象 public/protected 继承 T 吗 ?
        NO: return `nullptr` 表示 `转换错误` / dynamic_cast `抛出 异常 bad_cast`

[2] result 必须 (由 程序员) 显式检查 / 已隐含 被 dynamic_cast 实现 检查过了

2) 空引用 `不可行、也不合理` 

    void fr(Ival_box& r)
    {
        Ival_slider& is = dynamic_cast<Ival_slider&>(r);
        // ... 使用 is ...
    }

5 static_cast 和 dynamic_cast

(1) 本质差异 
    
    ——————————————————————————————————————————
                    `是否 检查` 要 `转换的对象` 
    ——————————————————————————————————————————  
    dynamic_cast |  是 + 运行时检查 
    ——————————————————————————————————————————  
    static_cast  |  否                           
    ——————————————————————————————————————————  

dynamic_cast 可从 多态虚基类 转换到 派生类 或 兄弟类, static_cast 不行

(2) static_cast 在 类层次中导航 的意义

非虚基类 转换: 不检查 要转换的对象, 靠 coder 保证 转换的有效性, 没有 dynamic_cast 的 运行时额外开销

(3) 均 `不能 转换到 private 基类`    

22.2 双重分发 和 访客

1 双重分发: 依次为 2个 多态类型 的 操作数 s1 和 s2 选择 正确的 同名 vf 版本

    class Shape
    {
    public:
        virtual bool intersect(const Shape&) const = 0;
        virtual bool intersect(const Circle&) const = 0;
    }:
    
    s1.intersect(s2)
    |            |
    |            |
    |/           |/
  Shape&        Shape& 
      
             Shape
             /  \
            /    \
        Circle  Triangle 
        
        多种排列组合 + 调换 2 个对象 的顺序 
        
        |
        |   问题 
        |/
    所需 vf 呈 指数增长 
        |
        |   解决 1
        |/
    用 非成员函数 intersect(Shape&, Shape&)
        |
        |   解决 2
        |/
    `预先计算 查询表`: 保存 `所有类型组合 对应的 函数` 
      |
      | + `索引` 计算函数: typeid(dynamic_obj) 对 -> 查询表中  两两组合 相应的 索引
      |
      |/
    可 `加速 类型识别` 
    
        bool intersect(const Shape& s1, const Shape& s1)
        {
            auto i = index(typeid(s1), typeid(s2) );
            return intersect_tbl[i](s1, s2);
        }

2 访客

    Node                                Visitor 
        virtual accept(Visitor&) = 0        virtual accept(Node1&) = 0
                                            virtual accept(Node2&) = 0
    Node1                                   
        accept(Visitor& v)              Visitor1
            调 v.accetp(*this)- - - - - - - accept(Node1&)
                                            accept(Node2&)

22.3 ctor / dtor

(1) Ctor 中某个点 对象的 (动态) 类型 仅反映 当前已构造完成部分

=> 类层次 中 类 Ctor 中调 vf本类或更上层类 中的 version, 原因: 对象 还不是 更深层次类的 对象

(2) 可在 对象未完成的某个点vf / dynamic_cast / typeid 观察 Ctor/Dtor 过程

22.4 类型识别

0 typeid <type_info>: 返回它所处理的对象的类型

    (1) 实现: 可能 是 函数 
        
        class type_info; 
        const type_info& typeid(expression);
        
        return `标准库类型 type_info` 的 `const 引用` => 
            
            [1] typeid(类型名 / type_name) // 必须是 `完整定义` 的 类型
                            
            [2] typeid(表达式 / expr) // 必须指向  `完整定义` 的 类型 
    
    (2) 运算对象 -> 返回结果                        
        ———————————————————————————————————————————————————————————————————————————————————————
        typeid 的 
        运算对象 
        ———————————————————————————————————————————————————————————————————————————————————————
        引用                  |   typeid(r)   // 获得 r 引用 的 对象的类型 
        指针所指对象          |   typeid(*p)  // 获得 p 指向 的 对象的类型
        指针                  |   typeid(p)   // 获得 指针类型, 不常见的用法, 通常是 用错了 
        ———————————————————————————————————————————————————————————————————————————————————————
        expr 值为 `nullptr` 的     |   typeid(expr) 抛出异常 std::bad_typeid 
        多态类型的指针 或 引用  |
        ———————————————————————————————————————————————————————————————————————————————————————
        `不是 多态类型`           |   结果在 `编译时确定`, 无须 运行时 对 expr 求值 
        或 `不是左值`           |
        ———————————————————————————————————————————————————————————————————————————————————————
        引用 / 解引用的指针     |   返回 `定义对象 时 使用的类型` 
        + 其类型为 `多态类型`   |
        ———————————————————————————————————————————————————————————————————————————————————————
                
        typeid(r).name() 
        
    (3) `C++ 不保证` 每个 `类型 只有1个 type_info 对象` 
            
1 扩展类型信息
    
    (1) `type_info 对象` 只保存 `最少的 类型信息`   

22.5 RTTI 使用 / 误用

RTTI 的正确使用场景: 如 用 RTTI 实现 对象 I/O 系统(22.1.1)

dynamic_cast 实现.jpg 同时包含 重复基类 和 虚基类` 的 `框架`.jpg 访客模式.jpg typeid: 扩展类型信息.jpg 建议.jpg

相关文章

  • 12_反射

    RTTI RTTI(RunTime Type Information)运行时类型信息,能够在程序运行时发现和使用类...

  • 只想把基础打好之-类型信息

    运行时类型信息使得你可以在程序运行时发现和使用类型信息 为什么需要RTTI(运行时类型信息):比如,我们使用```...

  • RTTI下的C++的向下转型

    什么是RTTI? RTTI是“Runtime Type Information”的缩写,意思是:运行时类型信息。它...

  • Java编程思想(十一)

    第14 章 类型信息 RTTI(Run Time Type Identification)运行时类型信息使得我们...

  • Java类型信息详解

    2.3 Java类型信息详解 运行时类型信息(RTTI)使得我们可以在程序运行时发现和使用类型信息,其工作原理是C...

  • 类型信息

    运行时的类型信息使得你可以在程序运行时发现和使用类型信息 为什么需要RTTI(Runtime Type Infor...

  • Java编程思想(十三) 类型信息

    运行时类型信息使得你可以在程序运行时发现和使用类型信息。 1、为什么需要RTTI 在运行时,识别一个对象的类型。 ...

  • Thinking in Java-类型信息

    运行时类型信息使得你可以在程序运行时发现和使用类型信息 为什么需要RTTI(Run-Time Type Ident...

  • Java重温-类型信息12

    这一章节部分是围绕RTTI(RunTime Type Information 运行时类型信息)展开的,运行时类型信...

  • 类型信息-01

    运行时类型信息1.RTTI,它假设我们在编译时已经知道了所有的类型信息。使用rtti可以查询基类引用所指向的对象的...

网友评论

    本文标题:22章 RTTI / 运行时类型信息

    本文链接:https://www.haomeiwen.com/subject/iegivrtx.html