Open Mq-b opened 1 year ago
我觉得采用“运行时检查”的概念就很不好,混淆了语义和实现,应该抛弃之。
dynamic_cast<T *>(v)
的良构问题,忽略 cv 限定和转换为 void *
的话:
T
不是完整类类型或者 v
的类型不是指向完整类类型的指针,则非良构;v
的类型是指向多态类型的指针,则良构;良构情况的语义问题(C++ 现行标准的文本写得很复杂,因为引入了“运行时检查”的概念,下面这个版本等效):
v
是 nullptr
则结果是 nullptr
,否则继续;v
的最派生对象 u
;u
的所有 T
基类子对象 t_1, ..., t_n
;1<=i<=n
使 t_i
和 v
有基类子对象关系(t_i
是 v
的基类子对象或 v
是 t_i
的基类子对象或 v
就是 t_i
),则结果是指向 t_i
的指针(这里不需要 n=1
);n=1
,则结果是指向 t_1
的指针;nullptr
。C++ 标准的规定:
void *
、从 nullptr
转换,都算是“运行时检查”。这里的重点在于:
因此
dynamic_cast< 新类型 >( 表达式 )
如果 表达式 是到多态类型Base
的指针或引用,且 新类型 是到Derived
类型的指针或引用,那么会进行运行时检查。
这个说法不够全面。而
如果是多态类型的话: 执行行运行时类型检查
这个说法和现行 C++ 标准不一致。
最后,从实现效率考虑,假设编译器对 v
的情况一无所知,并采用通常的实现:
virtual
基类,且基类的 offset 是 0,则没有任何运行时开销;virtual
基类,且基类的 offset 不是 0,则运行时需要判断 nullptr
并条件加减数;virtual
基类,则运行时需要判断 nullptr
并进行某些 indirection,这个开销可能比 5 低,也可能和 5 一起处理;仓库原文里
对不明确的指针的转换将失败
这句话本身就很不明确:
v
指向的对象确实是某个派生类对象的一部分,则以被转换的指针所指向的对象为基类子对象的情况优先,这时最派生对象可以含有其他 T
基类子对象;T
基类子对象。应该特别注意,从基类到派生类,有两种模式(取决于 v
具体指向最派生对象的哪个基类子对象)。
仓库原文里
可以在整个类层次结构中移动指针,包括向上转换、向下转换
不够全面——可以向上、向下、旁支转换。
还应该注意,派生类转基类,如果基类是 virtual
无歧义可访问,那么 static_cast
不可以,但 dynamic_cast
可以,此时也不需要派生类是多态类型(具有 virtual
基类并不会导致类型成为多态类型,只有 virtual
函数才会导致)。
嗨,亲!谢谢你的来信,记得常联系哦!
您好,您所发送的邮件我已收到,但并不能代表着邮件已被读取或被正确理解。
信已收到,谢谢~~~
除了类不变、派生类到基类、任意类到 void *、从 nullptr 转换,都算是“运行时检查”。
学到了🤣。
不过能详细聊一下嘛,以及
“运行时检查”不一定是从基类到派生类,也可以是表面上没有继承关系的类
能举个例子嘛?
@GeeLaw
其实另外一部分就提到了
不够全面——可以向上、向下、旁支转换。
如下:
struct B1 { virtual f1();};
struct B2 { virtual f2();};
struct D: B1, B2 {};
void fun(B1 *p) {
auto p = dynamic_cast<B2*>(p);
}
此处从B1*
向B2*
的sidecast就是
“运行时检查”不一定是从基类到派生类,也可以是表面上没有继承关系的类
这个转换要藉由struct D
这样的旁支选择无歧义的时候才能进行
@Mq-b 后面问题的例子:
struct B1 { virtual ~B1(); };
struct B2 { };
struct D : B1, B2 { };
D d;
B1 *b1 = &d;
// B1 和 B2 表面上没有继承关系
// b1 是 B1 * 而 B1 是多态类型
// b1 指向对象的最派生对象是 d
// d 里面的 b1 是公开基类 B1 的子对象
// d 有无歧义基类 B2 且 B2 是 D 的公开基类
// 转换得到这个 B2 基类子对象的指针
B2 *b2 = dynamic_cast<B2 *>(b1);
@dynilath 的例子没有体现 B2
不需要是多态类型,另外
这个转换要藉由
struct D
这样的旁支选择无歧义的时候才能进行
准确来说,是不考虑访问性时选择无歧义且基类关系公开,注意有惟一公开基类、不惟一基类的时候,转换失败。
第一部分的问题,单纯是 C++ 标准,见 expr.dynamic.cast,下面假设 dynamic_cast<C *>(v)
且 v
是 V *
且 C
和 V
都是完整类类型且 v
不等于 nullptr
,无关的内容都略:
C
就是 V
,则结果是 v
;C = B
是 V = D
的基类,那么结果是 v
的惟一 B
基类子对象;如果 D
的基类 B
歧义或者不可访问,则程序非良构;V
必须是多态类型;nullptr
转换);void *
);v
指向对象的最派生对象是 u
:
a. 如果 v
指向 u
的某个 C
基类子对象 c
的某个公开基类子对象(用 v <= c <= u
表示),且不存在不是 c
的 c'
使 v <= c' <= u
且 c'
是 C
,则结果是指向 c
的指针;
b. 如果 v
指向 u
的某个公开基类子对象,且 u
具有惟一的 C
基类子对象 c
,且 C
是公开基类,则结果是指向 c
的指针;这里比较变态的点是关于 public
和可访问性的细节,我的“等效”表述里面忘记考虑。But still, 这个标准相当难读,而且有不少“陷阱”。
上面的叙述里“运行时检查”排除了 2、3、6、7。
考虑到“运行时检查”出现在标准文本中,我们是不是该考虑下提个编辑 issue?@GeeLaw
我懒,而且这只是不幸的选词,而不是技术问题。
而且这只是不幸的选词,而不是技术问题。
……正因此我建议通过 editorial issue 处理,如果是技术问题反而不该这么做。
写的是:
dynamic_cast< 新类型 >( 表达式 )
如果 表达式 是到多态类型Base
的指针或引用,且 新类型 是到Derived
类型的指针或引用,那么会进行运行时检查。除此之外其他时候基本上是没有这种额外开销的。
并且它也可以用作其他的转换。
无虚函数,自然没有所谓的运行时检查。
当然了,没开销的时候说明不该使用
dynamic_cast
。感觉应该改成