What static_cast<> is actually doing
http://www.codeproject.com/useritems/static_cast.asp
Introduction
Most programmers learn C before C++ and get use to C style casting. When writing C++, sometimes we may be confused about when to use static_cast<> and when to use reinterpret_cast<>. In this article, I will illustrate what static_cast<> is actually doing and will show some cases that will lead to errors.
Generic Types
float f = 12.3; float* pf = &f; // static cast<> int n = static_cast<int>(f); // OK, n = 12 //int* pn = static_cast<int*>(pf); // Error, types pointed to are unrelated void* pv = static_cast<void*>(pf); // OK int* pn2 = static_cast<int*>(pv); // OK, but *pn2 is rubbish // reinterpret_cast<> //int i = reinterpret_cast<int>(f); // Error, the compiler know you should call static_cast<> int* pi = reinterpret_cast<int*>(pf); // OK, but *pn is actually rubbish, same as *pn2
In short, static_cast<> will try to convert, e.g. convert float-to-integer, while reinterpret_cast<> simply change the compiler's mind to reconsider that object as another type.
Pointer Types
Pointer casting are a bit complicated, we will use the following classes for the rest of the the article.
class CBaseX
{
public:
int x;
CBaseX() { x = 10; }
void foo() { printf("CBaseX::foo() x=%d\n", x); }
};
class CBaseY
{
public:
int y;
int* py;
CBaseY() { y = 20; py = &y; }
void bar() { printf("CBaseY::bar() y=%d, *py=%d\n", y, *py); }
};
class CDerived : public CBaseX, public CBaseY
{
public:
int z;
};
Case 1: Casting between unrelated classes
// Convert between CBaseX* and CBaseY* CBaseX* pX = new CBaseX(); // CBaseY* pY1 = static_cast<CBaseY*>(pX); // Error, types pointed to are unrelated CBaseY* pY2 = reinterpret_cast<CBaseY*>(pX); // Compile OK, but pY2 is not CBaseX // pY2->bar(); // System crash!!
As we learnt in the generic types example, static_cast<> will fail if you try to cast an object to another unrelated class, while reinterpret_cast<> will always success by "cheating" the compiler to believe that the object is really that unrelated class.
Case 2: Casting to related classes
1. CDerived* pD = new CDerived(); 2. printf("CDerived* pD = %x\n", (int)pD); 3. 4. // static_cast<> CDerived* -> CBaseY* -> CDerived* 5. CBaseY* pY1 = pD; // OK, implicit static_cast<> casting 6. printf("CBaseY* pY1 = %x\n", (int)pY1); 7. CDerived* pD1 = static_cast<CDerived*>(pY1); // OK, now pD1 = pD 8. printf("CDerived* pD1 = %x\n", (int)pD1); 9. 10. // reinterpret_cast 11. CBaseY* pY2 = reinterpret_cast<CBaseY*>(pD); // OK, but pY2 is not CBaseY* 12. printf("CBaseY* pY2 = %x\n", (int)pY2); 13. 14. // unrelated static_cast<> 15. CBaseY* pY3 = new CBaseY(); 16. printf("CBaseY* pY3 = %x\n", (int)pY3); 17. CDerived* pD3 = static_cast<CDerived*>(pY3); // OK, even pY3 is just a "new CBaseY()" 18. printf("CDerived* pD3 = %x\n", (int)pD3); ---------------------- output --------------------------- CDerived* pD = 392fb8 CBaseY* pY1 = 392fbc CDerived* pD1 = 392fb8 CBaseY* pY2 = 392fb8 CBaseY* pY3 = 390ff0 CDerived* pD3 = 390fec
Noted that when static_cast<> CDerived* to CBaseY* (line 5), the result is CDerived* offset by 4. To know what static_cast<> is actually doing, we have to take a look at the memory layout of CDerived.
|
As shown in the diagram, CDervied's memory layout contains two objects, CBaseX and CBaseY, and the compiler knows this. Therefore, when you cast CDerived* to CBaseY*, it adds the pointer by 4, and when you cast CBaseY to CDerived, it subtracts the pointer by 4. However, you can do this even if it is not a CDerived (line 14-18) [1].
Of course, problem happens only if you have multiple inheritance. static_cast<> and reinterpret_cast<> makes no different if you are casting between CDerived to CBaseX.
Case 3: Casting back and forth between void*
Because any pointer can be cast to void*, and void* can be cast back to any pointer (true for both static_cast<> and reinterpret_cast<>), error may occur if not handled carefully.
CDerived* pD = new CDerived(); printf("CDerived* pD = %x\n", (int)pD); CBaseY* pY = pD; // OK, pY = pD + 4 printf("CBaseY* pY = %x\n", (int)pY); void* pV1 = pY; // OK, pV = pY printf("void* pV1 = %x\n", (int)pV1); CDerived* pD2 = static_cast<CDerived*>(pV1); // pD2 = pY, but we expect pD2 = pY - 4 printf("CDerived* pD2 = %x\n", (int)pD2); // pD2->bar(); // System crash ---------------------- output --------------------------- CDerived* pD = 392fb8 CBaseY* pY = 392fbc void* pV1 = 392fbc CDerived* pD2 = 392fbc
Once we have cast the pointer to void*, we can't cast it back to the original class easily. In the above example, the only way to get back a CDerived* from a void* is to cast it to a CBaseY* and then to CDerived*. But if we are not sure whether it is CBaseY* or CDerived*, then we have to use dynamic_cast<> or typeid [2].
Footnote
- dynamic_cast<> on the other hand, can guard against casting a generic CBaseY* to CDerived*
- dynamic_cast<> requires the classes to be "polymorphic", i.e. contains "virtual" function, and hence can't be void*
References
- [MSDN] C++ Language Reference -- Casting
- Nishant Sivakumar, Casting Basics - Use C++ casts in your VC++.NET programs
- Juan Soulie, C++ Language Tutorial : Type Casting
History
- 3rd February 2006: Initial version uploaded.
Sam NG
|
Click here to view Sam NG's online profile. |
Other popular articles:
|

浙公网安备 33010602011771号