[英]Failed and ill-formed casts
您能否解释一下ill-formed cast
与failed cast
之间的区别? 例如:
class A { virtual void f(); };
class B { virtual void g(); };
class D : public virtual A, private B { };
void g() {
D d;
B* bp = (B*)&d; // cast needed to break protection
A* ap = &d; // public derivation, no cast needed
D& dr = dynamic_cast<D&>(*bp); // fails
ap = dynamic_cast<A*>(bp); // fails
bp = dynamic_cast<B*>(ap); // fails
ap = dynamic_cast<A*>(&d); // succeeds
bp = dynamic_cast<B*>(&d); // ill-formed (not a run-time check)
}
尽管有名称,但是当您使用dynamic_cast
进行向上转换(派生->基础)时, static_cast
转换在编译时完成,其行为与static_cast
或隐式转换相同-如果基础不明确或无法访问,则程序异常格式,表示编译器必须产生诊断信息。 §5.2.7[expr.dynamic.cast] / p5:
[对于表达式
dynamic_cast<T>(v)
:]如果
T
是“指向cv1 B
指针”并且v
具有类型“指向cv2 D
指针”,使得B
是D
的基类,则结果是指向v指向的D
对象的唯一B
子对象的指针。类似地,如果T
是“对cv1 B
引用”并且v
具有cv2 D
类型,使得B
是D
的基类,则结果是v
引用的D
对象的唯一B
子对象。 67其结果是左值如果T
是一个左值参考,或x值如果T
是一个rvalue参考。 在指针和引用两种情况下,如果cv2
cv资格大于cv1
的资格,或者B
是D
的不可访问或模棱两可的基类,则程序是cv2
。67 v指向或引用的最派生对象(1.8)可以包含其他B对象作为基类,但这些对象将被忽略。
在其他情况下(向下浇铸或侧向浇铸),则在运行时执行检查。 如果失败,则转换结果是指针转换为空指针,而引用转换为std::bad_cast
异常。
让我们从头开始,看看每种情况:
class A { virtual void f() {} };
class B { virtual void g() {} };
class D : public virtual A, private B { };
void g() {
D d;
B* bp = (B*)&d;
[continue...]
C ++强制转换( reinterpret_cast
例外,它仅强制转换原始指针值而无需进行任何调整或算术运算)不允许使用C样式“忽略”继承访问级别( https://stackoverflow.com/a/3674955/1938163 )演员可以。
使用上面的代码bp
是一个指向有效对象的指针,但是访问级别被完全绕开。 这可能是个问题吗?
答案是肯定的,以下面为例:
class A { virtual void f() {} };
class B { virtual void g() {}
public:
~B() {cout << "B's destructor";} // You can destroy B objects but NOT D objects from B* pointers
};
class D : public virtual A, private B {
~D() {cout << "D's destructor";}
};
void g() {
D *d = new D();
B* bp = (B*)d; // Bypass access permissions
delete bp; // This shouldn't happen! D's destructor will NOT be called! Undefined Behavior!
使用-Wold-style-cast -Werror
可避免此问题(以及其他几个问题: https : -Wold-style-cast -Werror
)
继续您的示例,我们有
A* ap = &d;
这是完全合法的选择。 以下是不合法的演员表:
D& dr = dynamic_cast<D&>(*bp);
出于可读性原因,引用标准并替换一些单词:
(N3690-§5.2.7-8)
如果D是D&指向或引用的类类型,则运行时检查在逻辑上执行如下:—如果在bp指向(引用)的最派生对象中bp指向(引用) 公共基类的子对象D对象,并且如果仅从bp指向(引用)的子对象派生出类型D的一个对象,则指向该D对象的结果点(引用)。
因此访问权限错误,并且转换失败。 它不是格式错误,只是失败(稍后阅读以了解区别)。
如果您要求一个指针,您将得到一个NULL
,但是由于引用需要绑定到对象,因此上面的方法会引发异常(这是在这里做的唯一明智的事情)。
随后的演员表也并非格式错误,只是明显的错误。 通常,您不能从基址指针强制转换为另一个基址( https://stackoverflow.com/a/7426562/1938163 ),但由于此处涉及的基类是多态的,因此应允许以下内容并有效:
ap = dynamic_cast<A*>(bp);
...如果bp指向(引用)最派生对象的公共基类子对象,并且最派生对象的类型具有类型A的基类,即明确且公共,则结果指向(引用)到最派生对象的A子对象。
但同样:权限使事情出错,并且转换失败( 不是格式错误,再次只是失败)。
由于ap
为NULL
ap
已经是无效的转换
bp = dynamic_cast<B*>(ap);
但是,如果ap
不为NULL
,对于上面引用的相同段落,强制转换将始终失败:
...如果ap指向(引用)最派生对象的公共基类子对象,并且派生最多的对象的类型为B, 且类型明确且是public ,则结果指向(引用)到最派生对象的B子对象。
唯一成功的演员是
ap = dynamic_cast<A*>(&d);
D
对象指针被强制转换为公共基类。
最后的演员表最终格式不正确
bp = dynamic_cast<B*>(&d);
因为根据标准
(N3690-§5.2.7-5)
(dynamic_cast的)
如果B *是“指向cv1 B的指针”,并且&d具有“指向cv2 D的指针”,使得B是D的基类,则结果是指向&d所指向的D对象的唯一B子对象的指针。
...
在指针和引用两种情况下,如果cv2的cv资格大于cv1的资格,或者B是D的不可访问或模棱两可的基类 , 则程序是错误的。
总计:3个失败的转换(一个异常抛出),一个成功,一个格式错误。
最后:失败的强制转换是无法完成的强制转换,但可以根据标准规则进行处理:
如果v的值在指针情况下为空指针值,则结果为类型T的空指针值。如果C是T指向或引用的类类型,则运行时检查在逻辑上执行如下:
...(与上述规则相同)
—否则,运行时检查将失败。
转换为指针类型失败的值是所需结果类型的空指针值。 转换为引用类型失败会引发异常
除非另有指示(不需要诊断),否则通常应该在您感兴趣的强制转换中,编译器实现会向格式错误的程序发出错误或警告:
B* bp = (B*)&d;
A* ap = &d;
D& dr = dynamic_cast<D&>(*bp); // This is a runtime error
ap = dynamic_cast<A*>(bp); // this is a runtime error
bp = dynamic_cast<B*>(ap); // this is a runtime error
ap = dynamic_cast<A*>(&d); // succeeds
bp = dynamic_cast<B*>(&d); // This is ill-formed and the compiler should warn about it
如果我出错了(很可能),请在下面的评论中写下来,我会立即修复。 谢谢!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.