美文网首页收藏
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

    相关文章

      网友评论

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

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