user1316208
user1316208

Reputation: 687

casting between two derived classes

is it legal to cast between pointers on classes that have common ancestor? Does the compiler notice such hierarchy and makes sure its safe (call 1) ? Or does the user have to go through the hierarchy manually for it to be always safe (call 2) ?

say we have

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
}

edit: Made this code compilable and runnable

edit2: Added more comments

Upvotes: 5

Views: 3658

Answers (4)

Columbo
Columbo

Reputation: 60979

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

This is not safe.
Casts of the form (T) expr are, roughly speaking, converted into either static_cast or reinterpret_cast. [expr.cast]/4:

The conversions performed by

  • a const_cast (5.2.11),
  • a static_cast (5.2.9),
  • a static_cast followed by a const_cast,
  • a reinterpret_cast (5.2.10), or
  • a reinterpret_cast followed by a const_cast,

can be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply […]
If a conversion can be interpreted in more than one of the ways listed above, the interpretation that appears first in the list is used, even if a cast resulting from that interpretation is ill-formed.

You can ignore const_cast here as no qualification conversions are done in your code. static_cast suffices in both casts, the first one, (A*), and the second one, (B*). The first one is just fine. Upcasting is never an issue.
The second one induces undefined behavior. [expr.static.cast]/11:

A prvalue of type “pointer to cv1 B,” where B is a class type, can be converted to a prvalue of type “pointer to cv2 D”, where D is a class derived (Clause 10) from B, if a valid standard conversion from “pointer to D” to “pointer to B” exists (4.10), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is neither a virtual base class of D nor a base class of a virtual base class of D. […] If the prvalue of type “pointer to cv1 B” points to a B that is actually a subobject of an object of type D, the resulting pointer points to the enclosing object of type D. Otherwise, the result of the cast is undefined.

Note also that just because the static_cast triggers UB that doesn't mean it isn't selected (and replaced by reinterpret_cast).

The second and third casts base on the first one (which causes undefined behavior), thus talking about their validity is pointless.

Upvotes: 2

Jean-Baptiste Yunès
Jean-Baptiste Yunès

Reputation: 36401

Your casts are both legal and correct but very dangerous. You should use reinterpret_cast<> to tag them in your code. You can always cast any address of any type A to any other address of any type B and get your first address back. This is essentially what you've done:

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

and then dereference pa. This example works but it is so easy to make a mistake...

Upvotes: 0

Surt
Surt

Reputation: 16099

Unless you really really know what your doing, don't do that.

The casts are legal, but using them on anything but the correct class results in undefined behaviour, any use of b without further casts results in UB, which might work, do nothing or start WWIII.

The casts simply tells the compiler that is should consider the variable to be of another type (unless it is multiple inheritance), but as soon as the cast variable is used it must actual be legal to use it in the way the code does, using B's function table is no good if the object is a C or vice versa. As this is undefined behaviour the compiler might emit whatever code it feels is right.

Example

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;
}

Gives

0x22aa6f,0x22aa6f

Example with multiple inheritance

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();
}

output

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

  • c is the real address of the new
  • b is the cast to AX first which is c then to BX (which doesn't make any sense for AX) but b is just set to the same address as c
  • cx is b reinterpreted as CX, multi-inheritance kicks in and really change the address as if b was the 2nd class in the inheritance.
  • ca is the correct reinterpretation of b through AX and then CX.

The 2 calls to SomeFunc works despite the wrong address

  • the function call is found through the current type which is CX due to the last cast.
  • the wrong addresses are passed as this pointer
  • because this is not used it works, so I had to add some use.
  • now we have entered undefined behaviour due to the cast (BX*)((AX*)c which makes the cast (CX*)b do the wrong thing.

To check if it is safe you need to use dynamic_cast.

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

  return EXIT_SUCCESS;   
}

Upvotes: 2

ravi
ravi

Reputation: 10733

To check whether a cast is meaningful or not just use dynamic_cast. dynamic_cast will cast properly if cast is safe OR returns NULL (in case of pointers, for references it throws bad_cast exception ) if its not able to cast to target type.

For your question just think about whether this cast is meaningful. You are casting B class to C where these classes have no knowledge of each other. So, surely this cast would fail.

You are doing:-

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

This would fail(means won't even compile ) if given to dynamic_cast since classes involved are not polymorphic. Even if you make class B polymorphic cast would fail. Leave further casting.

One more thing you can cross cast using dynamic_cast safely assuming classes are polymorphic and cast is safe. For e.g:-

class A;
Class B;
Class C : public A, public B

A *a = new C;

You can cast this to sibling:-

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

Upvotes: 1

Related Questions