|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
PrologueIf someone were to ask me to name the single most exciting feature of C++,
I'd reply saying that it was the ability to perform casts from one type to
another. Of course this is purely a whimsical and highly irrational love I have
for casts and is not a reflection of any great technical pondering I might have
committed on casts and their usefulness. I mentally squeal with delight every
time I see code that looks something similar to That's when I took a look at the new C++ casting operators that were available and quickly discovered that, they were a much safer and smarter way for doing casts. This article will run through the various casting operators that are available and will suggest when and where you might want to use each of these casting operators. The entire article has been written from a managed extensions context and therefore I only discuss managed classes in this article. This has some interesting corollaries which I mention later in the article. Dangers of C-style castsThe single biggest problem with C-style casts is that they are absolutely and totally unsafe. No compile time or run time checks are made and you are left with full freedom to dig your own grave, as deep as you want to. There is nothing to stop you from casting a base class pointer that points to a base class object to a derived class pointer, which means that when you make your derived class method calls, catastrophe results most assuredly. Another problem is that you never know what kind of cast you are trying to
achieve. All casts you can do look the same as far as syntax goes. Obviously
this can be a pain in the neck when you are debugging. In addition some people
use the functional style casts where they do The C++ casting operatorsThere are five casting operators provided which serve to replace the old
C-style casts. Each has a specific purpose and intended usage which I explain
later down the article. Before that I'd like to touch on the subject of
polymorphic classes. Any class that has virtual functions is a polymorphic
class. Now this marks a major distinction among classes in the old unmanaged
world. But in the new managed world of C++ coding, every class is polymorphic,
because all managed classes derive implicitly from
static_castThe T t = static_cast<T>(expression);
An example usage would be something like :- CMainFrame* pMF = static_cast<CMainFrame*>(AfxGetMainWnd());
dynamic_castThe T t = dynamic_cast<T>(expression);
A sample usage of void Hello(Base* p1)
{
//...
Child *c1 = dynamic_cast<Child*>(p1);
if(c1)
{
// we can safely use c1
}
//...
}
const_castThe T t = const_cast<T>(expression);
A sample use would be something similar to :- void Abc(const Base* pB)
{
//...
Xyz(const_cast<Base*>(pB));
}
void Xyz(Base* pB)
{
//...
}
reinterpret_castTo be really honest I have no clue why they have this one because as far as I
see it, its just as bad as the old C style casts except that it advertises it's
lack of safety quite explicitly. You can use the T t = reinterpret_cast<T>(expression);
Basically the only safe place to use it would be where we cast from Type-1 to
Type-2 and later we cast back Type-2 to Type-1. Now we have the exact object
back as when we started this /* A and B are unrelated types */
A *a = reinterpret_cast<A*>(new B());
B *b = reinterpret_cast<B*>(a); //safe to use b
__try_cast
T t = __try_cast<T>(expression);
The exception that gets thrown is try
{
p2 = __try_cast<Base*>(p1);
//use p2 safely now
//...
}
catch(System::InvalidCastException*)
{
Console::WriteLine("blast!");
}
When to use and what?Well, we've seen 5 different casting operators and they are supposed to be replacements for the old C-style casts. It might be a little baffling to choose the correct cast operator when you are coding. To be really honest, till recently I had no clue when to use what! Anyway I have created certain imaginary casting scenarios and used each of the cast operators to try and do the casts [where permitted by the compiler] and I have made an attempt to demonstrate when and why some cast operators are not suitable and when they are suitable. Of course the recommendations are strictly my own and I do not claim that they might be the most suitable options, but then I intend to keep updating the article with valuable input from C++ gurus who happen to come across this article. Test classesThese are the test classes and enums that I have used in my experiments. /*
Our test base class
*/
__gc class Base
{
public:
int dummy;
};
/*
Our test derived class
*/
__gc class Child : public Base
{
public:
char anotherdummy;
};
/*
An independent test class
*/
__gc class Base2
{
};
enum CastType
{
tdynamic_cast,
tstatic_cast,
tconst_cast,
treinterpret_cast,
ttry_cast
};
DowncastingDowncasting is when you convert from a base class pointer to a derived class
pointer. I have written a function called Child* BaseToChild(Base* p,CastType casttype)
{
Child* retptr = NULL;
switch(casttype)
{
case tdynamic_cast:
retptr = dynamic_cast<Child*>(p);
break;
case tstatic_cast:
retptr = static_cast<Child*>(p);
break;
case tconst_cast:
/* This won't compile */
//retptr = const_cast<Child*>(p);
break;
case treinterpret_cast:
retptr = reinterpret_cast<Child*>(p);
break;
case ttry_cast:
try
{
retptr = __try_cast<Child*>(p);
}
catch(System::InvalidCastException*)
{
//...
}
break;
}
return retptr;
}
Unsafe downcastingBase *pBase1 = new Base();
Child *pChild1 = NULL;
I have //dynamic_cast
pChild1 = BaseToChild(pBase1,tdynamic_cast);
if(!pChild1)
{
Console::WriteLine("dynamic_cast - downcast failure");
}
else
{
Console::WriteLine("dynamic_cast - downcast success");
}
I only show the sample code for
Safe downcastingBase *pBase1 = new Child();
Child *pChild1 = NULL;
Alright now we have the Base object holding a Child object. Now it's
perfectly safe to downcast to the derived class. Again I called
Recommendation for downcastingIf you are absolutely sure that the cast is going to be safe, you can use any
of the four cast operators above, but I'd suggest that you use UpcastingUpcasting is when you cast from a derived class to one of the parent classes
in the inheritance chain. Usually upcasting is pretty much safe except in
certain rare situations that are actually a result of bad coding rather than
anything else. I demonstrate both scenarios below. Just like I had a function
for downcasting, I also have one for upcasting called Base* ChildToBase(Child* p,CastType casttype)
{
Base* retptr = NULL;
switch(casttype)
{
case tdynamic_cast:
retptr = dynamic_cast<Base*>(p);
break;
case tstatic_cast:
retptr = static_cast<Base*>(p);
break;
case tconst_cast:
/* This won't compile */
//retptr = const_cast<Base*>(p);
break;
case treinterpret_cast:
retptr = reinterpret_cast<Base*>(p);
break;
case ttry_cast:
try
{
retptr = __try_cast<Base*>(p);
}
catch(System::InvalidCastException*)
{
//...
}
break;
}
return retptr;
}
Safe upcastingBase *pBase2 = NULL;
Child *pChild2 = new Child();
I am casting from
Unsafe upcastingBase *pBase2 = NULL;
/* Intentionally create a bad pointer */
Child *pChild2 = reinterpret_cast<Child*>(new Base2());
Here I have intentionally created a
Recommendations for upcastingIn most situations upcasting should be quite safe except when you have a bad
derived class pointer (bad in the sense that it points to the wrong object).
Therefore my recommendation for upcasting is to use const_cast usageConsider the code below :- const Base *pB = new Base();
/* won't compile */
pB->dummy = 100;
You'll get a compiler error, because you are trying to modify a const_cast<Base*>(pB)->dummy = 100;
/* should show 100 on the console */
Console::WriteLine(pB->dummy);
Another useful application of void A::abc() const
{
const_cast<A* const>(this)->m_total++;
}
reinterpret_cast usageAs I already mentioned earlier, this is the most unsafe of all the C++ cast
operators and it's probably best to avoid using it. But then when porting old
code, you might want to convert the old style casts to Base *pB1 = NULL;
Base2 *pB2 = new Base2();
Let's first try to use /* Compiles, but fails */
pB1 = dynamic_cast<Base*>(pB2);
if(!pB1)
Console::WriteLine("dynamic_cast failed");
It compiles fine, but fails during run-time. Now let's try using /* Won't compile */
//pB1 = static_cast<Base*>(pB2);
Okay, great! That won't even compile because the compile-time check fails.
How about /* Compiles, but throws exception */
try
{
pB1 = __try_cast<Base*>(pB2);
}
catch(System::InvalidCastException*)
{
Console::WriteLine("__try_cast has thrown an exception");
}
Just like /* This is a sort of blind man's cast */
pB1 = reinterpret_cast<Base*>(pB2);
Oh boy! That compiled and also ran fine. No errors. /* Now we cast it back */
Base2* pDest = dynamic_cast<Base2*>(pB1);
if(pDest)
{
Console::WriteLine("Original pointer has been obtained");
}
Well, as you might have understood by now, it's safest to avoid using
ConclusionWell, the magic of casts haven't died yet, and casts live through the C++ cast operators and continue to enthrall C/C++ lovers all over the world. I am not sure that everything I have suggested in the article is nice and proper. But I trust that I'll get the required critical feedback to correct any erroneous statements and recommendations I might have made. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||