两个派生类之间的转换

时间:2014-11-04 11:18:39

标签: c++ class casting

在具有共同祖先的类的指针之间进行转换是否合法?编译器是否注意到这种层次结构并确保其安全(调用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:添加了更多评论

4 个答案:

答案 0 :(得分:2)

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

强制转换是合法的,但在正确的类上使用它们会导致未定义的行为,任何使用b而不进一步强制转换都会导致UB,这可能会起作用,什么也不做或者启动WWIII。

强制转换只是告诉编译器应该将变量视为另一种类型(除非它是多重继承),但是一旦使用了cast变量,它必须在代码的使用方式中合法使用它如果对象是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,然后转换为B,然后转换为BX(对AX没有任何意义),但b只是设置为与c相同的地址
  • cx被重新解释为CX,多继承启动并真正更改地址,就像b是继承中的第二个类一样。
  • ca是正确的重新解释b到AX然后是CX。

尽管地址错误,但对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;   
}

答案 1 :(得分:2)

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),
  •   
  • a static_cast后跟const_cast
  •   
  • a reinterpret_cast(5.2.10)或
  •   
  • a reinterpret_cast后跟const_cast
  •   
     

可以使用显式类型转换的强制转换表示法执行。   应用相同的语义限制和行为 [...]如果a   转换可以用多种列出的方式解释   在上面,甚至使用列表中首先出现的解释   如果由该解释产生的演员表格不合格。

您可以在此忽略const_cast,因为您的代码中未进行任何资格转换。 static_cast在两个演员阵容中都足够,第一个,(A*)和第二个,(B*)。 第一个就好了。上传永远不是问题 第二个引发未定义的行为。 [expr.static.cast] / 11:

  

prvalue类型“指向 cv1 B的指针”,其中B是类类型,   可以转换为“指向 cv2 D的指针”的prvalue,其中   如果是有效标准,DB派生的类(第10条)   存在从“指向D”的指针到“指向B的指针”的转换(4.10),    cv2 cv1 具有相同的cv资格,或更高的cv资格,B既不是D的虚拟基类也不是基地   D的虚拟基类的类。 [...] 如果是类型的prvalue   “指向cv1 B的指针”指向B,它实际上是一个子对象   类型D的对象,结果指针指向封闭   D类型的对象。否则,演员表的结果是未定义的。

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

第二个和第三个演员表基于第一个(导致未定义的行为),因此谈论它们的有效性毫无意义。

答案 2 :(得分:1)

要检查演员表是否有意义,请使用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);

答案 3 :(得分:0)

你的演员既合法又正确但非常危险。你应该使用reinterpret_cast&lt;&gt;在你的代码中标记它们。 您始终可以将任何类型A的任何地址投射到任何类型B的任何其他地址,并获取您的第一个地址。这基本上就是你所做的:

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

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