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)
网友评论