Casting between two derived classes
Is it possible to cast between pointers to classes that have a common ancestor? Does the compiler mark such a hierarchy and guarantee its safety (call 1)? Or does the user have to traverse the hierarchy manually so that it is always safe (challenge 2)?
let's say that
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 compileable and executable
edit2: more comments added
source to share
B* b = (B*)((A*)new C()); // this is done manually, i believe it is safe
It is not safe .
Letters of the form (T)
expr are roughly converted to static_cast
or reinterpret_cast
. [Expr.cast] / 4:
Conversions performed by
- a
const_cast
(5.2.11),- a
static_cast
(5.2.9),- a
static_cast
followed byconst_cast
,- a
reinterpret_cast
(5.2.10) or- a
reinterpret_cast
followed byconst_cast
,can be performed using an explicit type conversion letter. The same semantic constraints and behavior are used [...]
If a transformation can be interpreted in more than one of the ways listed above, the interpretation that appears first in the list is used, even if the cast resulting from that interpretation is ill-formed.
You can ignore this here const_cast
as no conversions are done in your code.
static_cast
missing in both throws, the first one (A*)
, and the second - (B*)
. The first one is just fine. Acceleration is never a problem.
The second invokes undefined behavior. [Expr.static.cast] / 11:
The class value "pointer to cv1
B
", whereB
is the type of the class, can be converted to a prvalue of type "pointer to cv2D
", whereD
is the derived class (clause 10) fromB
, if a valid standard conversion from "pointer toD
" to "pointer toB
" exists (4.10), cv2 is the same cv qualification as a higher cv qualification than cv1 andB
is neither a virtual base classD
nor a virtual base class base classD
. [...] If a value of type "pointer to cv1 B" points to aB
, which is actually a subobject of the type objectD
, the resulting pointer points to an object of typeD
.Otherwise, the result of the cast is undefined.
Note that just because it static_cast
launches UB, which does not mean that it is not selected (and replaced with reinterpret_cast
).
The second and third fundamentals are based on the first (which causes undefined behavior), so it makes no sense to talk about their reliability.
source to share
If you really don't know what you are doing, don't do it.
The assignments are legal, but using them on anything other than the correct class results in undefined behavior, any use b
with no further results results in UB being able to run, do nothing, or start WWIII.
The casts just tell the compiler to take into account that the variable is of a different type (unless it is multiple inheritance), but once a cast variable is used, it must be legal to use it in the way the code does using the B function table is not good if the object is C or vice versa. Since this behavior is undefined, the compiler can emit whatever code it thinks is correct.
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 - valid address
new
- b is a cast to AX first and then to BX (which doesn't make any sense for AX), but b is just set to the same address as c
- cx is interpreted as CX, multi-inheritance inheritance, and indeed changes the address as if b were the second class in inheritance.
- ca is the correct reinterpretation of b through AX and then CX.
2 calls to SomeFunc work despite the wrong address
- a function call was found via the current type, which is CX due to the last press.
- invalid addresses are passed as a
this
pointer - because
this
not used, so I had to add some usage. - we have now introduced undefined behavior due to the cast (BX *) ((AX *) c, which makes casting (CX *) b do the wrong thing.
To check if it's 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;
}
source to share
To check if the accent is significant or not just use dynamic_cast. dynamic_cast will cast correctly if the cast is safe, or returns NULL (in the case of pointers, for references it throws a bad_cast exception) if it cannot use the target type.
For your question, just think about whether this actor is significant. You are throwing class B to C where these classes don't know each other. So, of course, this cast will not work.
You do: -
B* b = (B*)(new C());
It won't work (the tool won't even compile) if set to dynamic_cast, since the classes involved are not polymorphic. Even if you do a polymorphic cast of class B, you will fail. Leave further casting.
Another thing you can cross-use using dynamic_cast of the intended classes safely is polymorphic, and casting is safe. For example for example: -
class A;
Class B;
Class C : public A, public B
A *a = new C;
You can give it to your brother: -
B *b = dynamic_cast<B*> (a);
source to share
Your throws are legal and correct, but very dangerous. You have to use reinterpret_cast <> to tag them in your code. You can always specify any address of A
any other address of any type B
and return your first address. This is essentially what you did:
A *pa = &some_a;
B *pb = reinterpret_cast<B *>(pa);
pa = reinterpret_cast<A *>(pb);
and then act out pa
. This example works, but it's so easy to make a mistake ...
source to share