C++ Type Casting safety?

Started by
11 comments, last by Nitage 18 years, 1 month ago
Hi, i have something like this:
class A{};
class B: public A{
public:
    char b[32];
    bool valid;
    B(){valid=true;}
};

class C: public A{
public:
    int c;
    bool valid;
    C(){valid=true;}
};

std::vector <A *>v;
B *b=new B();
C *c=new C();
v.push_back((A *)b);
v.push_back((A *)c);




Is it then safe to check if the object's type is B or C by doing:
if(((B *)v.at(0)).valid==true)
	//class=B
if(((C *)v.at(0)).valid==true)
	//class=C




I don't think so but it does work (Debug and Release mode MSVC++ 6.0), as long as the classes are different enough so the compiler can't cast them by itself. Is this safe or is there a safer or official way? Thanks.
Advertisement
No, it's not safe. The proper way is as follow:

class A{public:   virtual ~A() = 0 {}    // A needs a virtual destructor for polymorphic behavior};class B: public A{public:    char b[32];};class C: public A{public:    int c;};std::vector <A *>v;B *b=new B();C *c=new C();v.push_back(b);    // Casts from B to A are unnecessaryv.push_back(c);    // Casts from C to A are unnecessary



B* ptr_b = dynamic_cast<B*>(v.at(0));if(ptr_b == NULL) {    /* that was not a B */ }else{   /* that was a B */   ptr_b->b[0] = 42;}


However, it is preferable to directly rely on virtual functions placed in A (and thus shared between B and C), because using type queries like that requires that you add up a test for each new type in your hierarchy, which is brittle, and also is rather inefficient.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
You probably want to use RTTI and a dynamic_cast.
[google]

Edit: Doh, beaten.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
I tried using dynamic_cast but it gives an exception in KERNEL32.DLL at that line as class A is polymorphic. Adding the virtual destructor didn't solve it.

Quote:However, it is preferable to directly rely on virtual functions placed in A (and thus shared between B and C)...

That's true and that's how i intended it in the first place but i need this in a few special cases.

Edit: I think i'll just add a virtual function returning the type, overridden by derived classes.
The problem arises if you actually have an A* in your vector. When you typecast the A* to a B* and acces the valid member, you are accessing memory you don't own. The correct way would be to have an enumerated type or some type of flag in the base class.

enum ObjectType { OBJ_A, OBJ_B, OBJ_C };class A{    ObjectType type;    A() : type(OBJ_A) { }};class B : public A{    B() : A() { type = OBJ_B; }}class C : public A{    C() : A() { type = OBJ_C; }}


And as somebody said before, you will definately want virtual destructors if you are creating any memory in any of your derived classes.

Edit: Its okay to "play" with memory by typecasting. But you have to be absolutely certain you aren't overstepping your bounds. In your original class B, you declared a char[32] then the bool valid. In the original class C you declared an int c then the bool valid. This puts (valid) at a different location depeneding on which class the ptr is actually pointing to. So your original implementation is never safe because if you had a B* and changed b[4] to one, it would appear as though ((C*)&some_B)->valid would be true.
-------Harmotion - Free 1v1 top-down shooter!Double Jump StudiosBlog
Quote:Original post by blaze02
The correct way would be to have an enumerated type or some type of flag in the base class.


That's unsafe: the flag or enumerated type could be set incorrectly because of human error, while dynamic_cast works on any class that uses inheritance (and thus, has a virtual destructor). And it's also more difficult to manage an inheritance lattice with only enumerated types, while dynamic_cast will handle type lattices without a sweat.

There are other ways, for instance using virtual functions as a semantic replacement for dynamic_cast, which need about as much work as an enumeration-based system, but are type-safe and can handle lattices. Still, they rely on human intervention and as such are prone to semantic bugs.

Of course, the best way (and the one that is the most often available and thrust forward in OOP) would be to use virtual functions to represent operations, and rework the design so the inheritance tree fits together better.
Quote:
I tried using dynamic_cast but it gives an exception in KERNEL32.DLL at that line as class A is polymorphic. Adding the virtual destructor didn't solve it.


Did make sure you have RTTI switched on?
Quote:Original post by Nitage
Quote:
I tried using dynamic_cast but it gives an exception in KERNEL32.DLL at that line as class A is polymorphic. Adding the virtual destructor didn't solve it.


Did make sure you have RTTI switched on?


Ah no thanks! I thought it was on by default. It works now, thanks for all help.
RTTI... ick... so... slow.

Its not like it does any optimizations, it just embeds a lot of junk in the code so it knows which object is of which type.
-------Harmotion - Free 1v1 top-down shooter!Double Jump StudiosBlog
Did you run a test to see if using RTTI really makes a noticeable speed difference in a sample program? IMO, while it will have some impact, it's hardly going to slow your program to a crawl.

This topic is closed to new replies.

Advertisement