失败和格式错误的强制转换

Failed and ill-formed casts

本文关键字:转换 错误 格式 失败      更新时间:2023-10-16

你能解释一下ill-formed castfailed 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或隐式转换相同—如果基是不明确的或不可访问的,则程序是病态的,这意味着编译器必须产生诊断。§5.2.7 [expr.dynamic.cast]/p5:

[对于表达式dynamic_cast<T>(v):]

如果T是"指向cv1 B的指针"且v是"指向cv2 D的指针"类型因此,BD的基类,其结果是指向v所指向的D对象的唯一B子对象T是"对cv1 B的引用",v具有cv2 D类型,因此BD的基类,结果是唯一的B子对象v所引用的D对象。67结果为左值如果T是左值引用,如果T是右值引用,则为xvalue引用参考。在指针和引用的情况下,程序都是如果cv2具有比cv1更大的cv- qualified,或者如果BD的不可访问或有歧义的基类。

67指向或引用的最派生对象(1.8)v可以包含其他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例外,它只是转换原始指针值,根本没有调整或算术)不允许"忽略"继承访问级别(https://stackoverflow.com/a/3674955/1938163), C风格的类型转换可以。

在上面的代码中,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://stackoverflow.com/a/12765440/1938163)

继续刚才的例子,我们有

A* ap = &d;

,这是一个完全合法的向上转换。不合法的是以下类型转换:

D& dr = dynamic_cast<D&>(*bp);

从标准中引证并替换一些词以方便阅读:

(N3690 -§5.2.7 - 8)

如果D是类类型,D&点或引用,运行时检查逻辑执行如下:如果在bp指向(引用)的最派生对象中,bp指向(引用)一个公共基类如果只有一个D类型的对象是从指向(引用)的子对象派生出来的

将结果指向D对象。
因此

访问权限是错误的,强制转换失败。不是格式错误,只是失败了(稍后阅读以了解差异)。

如果你要求一个指针,你会得到一个NULL的指针,但是因为引用需要绑定到一个对象,上面抛出一个异常(这是这里唯一明智的事情)。

后面的强制转换也不是错误的,而是完全错误的。通常你不能从基指针强制转换到另一个基指针(https://stackoverflow.com/a/7426562/1938163),但因为基这里涉及的类是多态的,下面的操作应该是允许的并且是有效的:

ap = dynamic_cast<A*>(bp);

…如果bp指向(引用)最派生对象的公共基类子对象,则最派生对象的类型具有类型a的基类,该基类是明确且公开的,即结果指向最派生对象的A子对象。

但再次:权限出错,强制转换失败(不是格式错误,再次失败)。

后面的强制转换已经是无效的强制转换,因为apNULL

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比cv1具有更大的cv限定条件,程序是病态的,或者如果B是D的不可访问或有歧义的基类,程序是病态的。

Grand-total: 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

如果我有什么错(很可能)请写下来在下面的评论,我会立即修复我的帖子。谢谢!