Sign in to follow this  
Tree Penguin

C++ Type Casting safety?

Recommended Posts

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.

Share this post


Link to post
Share on other sites
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 unnecessary
v.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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Quote:
Original post by blaze02
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.


dynamic_cast only needs to traverse the type lattice to determine if an object can be converted to a given type, using vtbl information. RTTI will have no impact outside calls to typeid and dynamic_cast. On simple lattice traversals (the kind you can do with enumerations) it will probably be even faster than an equivalent hand-crafted system. On more complex lattice traversals, comparison is impossible since enumeration-based methods cannot perform them.

It uses less memory, too: in a program with 10 classes and an average of 1000 objects, the enumeration method adds 4000 bytes of overhead, while the RTTI method adds a few hundred cache-friendly bytes.

Share this post


Link to post
Share on other sites
Apart from significant increase in exe size I kinda doubt you would see significant performance loss without over-using dynamic cast and such.

Share this post


Link to post
Share on other sites
Not to mention that the extra information the compiler inserts for RTTI is required for exception handling - 2 for the price of 1.

Having said that, I always have RTTI disabled in release builds anyway.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this