在两个派生类之间进行强制转换

casting between two derived classes

本文关键字:之间 转换 派生 两个      更新时间:2023-10-16

具有共同祖先的类的指针之间进行强制转换是否合法?编译器是否注意到这样的层次结构并确保其安全(调用 1(?还是用户必须手动遍历层次结构才能使其始终安全(调用 2(?

说我们有

class A{};
class B:A{};
class C:A
{ 
public:
 int SomeFunc(){return 3;}
};
int _tmain(int argc, _TCHAR* argv[])
{
    B* b = (B*)((A*)new C()); // this is done manually, i believe it is safe
  ((C*)b)->SomeFunc(); // is this safe? this is the cast in question
  return ((C*)((A*)b))->SomeFunc(); // this is done manually, i believe it is safe
}

编辑:使此代码可编译和运行

edit2:添加了更多评论

B* b = (B*)((A*)new C()); // this is done manually, i believe it is safe

这是不安全的
粗略地说,(T) expr 形式的强制转换转换为 static_castreinterpret_cast 。[expr.cast]/4:

执行的转换

  • a const_cast(5.2.11(,
  • a static_cast(5.2.9(,
  • 一个static_cast后跟一个const_cast
  • a reinterpret_cast (5.2.10(,或
  • 一个reinterpret_cast后跟一个const_cast

可以使用显式类型转换的强制转换表示法来执行。 相同的语义限制和行为适用 [...]
如果 可以通过列出的多种方式解释转换 上面,使用了列表中首先出现的解释,甚至 如果由该解释产生的石膏格式不正确。

您可以在此处忽略const_cast因为代码中没有进行限定转换。 static_cast两个演员阵容中都足够了,第一个,(A*),第二个,(B*)。第一个很好。向上投射从来都不是问题。
第二个诱发未定义的行为。[expr.static.cast]/11:

类型为">指向 cv1 的指针"的 prvalue B ,其中 B 是类类型, 可以转换为"指向 CV2 D 的指针"类型的 prvalue ,其中 D是从B派生的类(第10条(,如果是一个有效的标准 存在从"指向D的指针"到"指向B的指针"的转换(4.10(, CV2 与 CV1 具有相同的 CV 资格,或比 CV1 更高的 CV 资格,并且B既不是D的虚拟基本类,也不是基础类 D的虚拟基类的类。[...]如果类型的 prvalue "指向 cv1 B 的指针"指向一个B,该实际上是 类型 D 的对象,生成的指针指向封闭 类型 D 的对象。否则,转换的结果是未定义的。

另请注意,仅仅因为static_cast触发了 UB,并不意味着它没有被选中(并替换为 reinterpret_cast (。

第二个和第三个转换基于第一个(这会导致未定义的行为(,因此谈论它们的有效性是没有意义的。

除非你真的知道自己在做什么,否则不要那样做。

强制转换是合法的,但是在正确的类之外的任何东西上使用它们会导致未定义的行为,任何在没有进一步转换的情况下使用b都会导致 UB,这可能有效、什么都不做或开始第三次世界大战。

强制转换

只是告诉编译器应该将变量视为另一种类型(除非它是多重继承(,但是一旦使用了强制转换变量,以代码的方式使用它必须是合法的,如果对象是 C,使用 B 的函数表是不好的,反之亦然。由于这是未定义的行为,编译器可能会发出它认为正确的任何代码。

class AA { };
class BB { };
class CC : public AA, public BB { };
int main () {
    CC cc; // address is 0x22aa6f
    BB* bb = &cc; // bb now is 0x22aa6f
    cout << &cc << "," << bb << "n";
    return EXIT_SUCCESS;
}

0x22aa6f,0x22aa6f

具有多重继承的示例

class AX{ int a = 1; };
class BX{ int b = 2; };
class CX:AX,BX {
public:
 int c = 3;
 int SomeFunc(){cout << "SomeFunc " << c << " "; return c;}
};
int cast() {
  CX* c;
  BX* b = (BX*)((AX*)(c = new CX())); // this is done manually, i believe it is safe
  cout << "c=" << c << ", b=" << b << ", cx=" << ((CX*)b) << ", ca=" << ((CX*)((AX*)b)) << endl;
  ((CX*)b)->SomeFunc(); // is this safe? this is the cast in question
  return ((CX*)((AX*)b))->SomeFunc(); // this is done manually, i believe it is safe
}
int main () {
    return cast();
}

输出

c=0x60003ac70, b=0x60003ac70, cx=0x60003ac6c, ca=0x60003ac70
SomeFunc 2 SomeFunc 3

  • c 是new的真实地址
  • b 首先是转换为 AX,然后是
  • c,然后转换为 BX(这对 AX 没有任何意义(,但 b 只是设置为与 c 相同的地址
  • cx 被 b 重新解释为 CX,多重继承开始并真正更改地址,就好像 b 是继承中的第 2 类一样。
  • ca 是通过 AX 和 CX 对 b 的正确重新解释。

尽管地址错误,但对 SomeFunc 的 2 次调用仍有效

  • 函数调用是通过当前类型找到的,由于上次强制转换,该类型为 CX。
  • 错误的地址作为指针传递this
  • 因为this没有使用它,所以我不得不添加一些用途。
  • 现在我们已经输入了未定义的行为,因为强制转换(BX*(((AX*(c使强制转换(CX*(b做错了事情。

要检查它是否安全,您需要使用dynamic_cast。

int main() {
  A* AP = new C();
  C* CP = dynamic_cast<C*>(A);
  if (CP != nullptr)
    CP->SomeFunc();
  return EXIT_SUCCESS;   
}

要检查强制转换是否有意义,只需使用 dynamic_cast。 如果强制转换是安全的,dynamic_cast将正确转换,或者如果它无法转换为目标类型,则返回 NULL(在指针的情况下,对于引用它会抛出bad_cast异常(。

对于你的问题,想想这个演员阵容是否有意义。您正在将 B 类转换为 C,其中这些类彼此不了解。所以,这个演员阵容肯定会失败。

你正在做:-

B* b = (B*)(new C());

如果给dynamic_cast,这将失败(意味着甚至不会编译(,因为所涉及的类不是多态的。即使你使B级多态性铸造也会失败。留下进一步的铸造。

还有

一件事,您可以使用dynamic_cast安全地交叉转换,假设类是多态的并且强制转换是安全的。例如:-

class A;
Class B;
Class C : public A, public B
A *a = new C;

你可以把它投给兄弟姐妹:-

B *b = dynamic_cast<B*> (a);

你的演员阵容既合法又正确,但非常危险。应使用 reinterpret_cast<> 在代码中标记它们。您始终可以将任何类型的A的任何地址转换为任何类型的任何其他地址B并取回您的第一个地址。这基本上是你所做的:

A *pa = &some_a;
B *pb = reinterpret_cast<B *>(pa);
pa = reinterpret_cast<A *>(pb);

然后取消引用pa.这个例子有效,但很容易犯错误......