Using __try_cast (old syntax, Managed Extension for C++) for Dynamic Casting
For a complete Typecasting in C++, please read C and C++ Typecasting. C++ supports the idea of casting, wherein you tell the compiler to convert one type into another to use it in an expression. Although casting can be useful, it can also be dangerous, and the __try_cast keyword has been introduced in the Managed Extensions for C++ to help make the operation safer. The following code fragment shows both safe and unsafe casting:
The compiler raises an error on the last line, complaining that it can’t convert a Vehicle* to a Car*. The problem is that a Vehicle pointer could point to any object derived from Vehicle, such as a Truck or a Bus. Implicitly casting from a Car to a Vehicle is fine because a Car is a Vehicle; going the other way doesn’t work because not every Vehicle is a Car. The way around this issue is to use the __try_cast construct, like this: |
try
{
Car* pc2 = __try_cast<Car*>(pv);
}
catch(System::InvalidCastException* pce)
{
Console::WriteLine("Cast failed");
}
At run time, __try_cast checks the object on the other end of the pointer to see if it has the same type as the object you’re trying to cast to. If it does, the cast works; if it doesn’t, an InvalidCastException is thrown. dynamic_cast construct supported by standard C++. The difference is that __try_cast throws an exception if the cast fails.
safe_cast for Dynamic Casting (new syntax)
safe_cast allows you to change the type of an expression and generate verifiable MSIL code. The syntax is shown below.
Syntax | [cli]::safe_cast<type-id>(expression) |
|
|
Parameters | Description |
type-id | A handle to a reference or value type, a value type, or a tracking reference to a reference or value type. |
source | An expression that evaluates to a handle to a reference or value type, a value type, or a tracking reference to a reference or value type. |
Table 3 |
The expression safe_cast<type-id>(expression) converts the operand expression to an object of type type-id. The compiler will accept a static_cast in most places that it will accept a safe_cast. However, safe_cast is guaranteed to produce verifiable MSIL, where as a static_cast could produce unverifiable MSIL. static_cast, safe_cast invokes user-defined conversions. safe_cast is a keyword defined inside the cli namespace, which is a compiler-defined namespace. safe_cast does not apply a const_cast (cast away const). safe_cast is in the cli namespace. One example of where the compiler will not accept a static_cast but will accept a safe_cast is for casts between unrelated interface types. With safe_cast, the compiler will not issue a conversion error and will perform a check at runtime to see if the cast is possible.
1. Create a new CLR Console Application project named mysafecast. Try the following code.
// mysafecast.cpp : main project file.
// compile with: /clr
#include "stdafx.h"
using namespace System;
interface class I1
{
// more codes...
};
interface class I2
{
// more codes
};
interface class I3
{
// more codes
};
ref class X : public I1, public I2
{
// more codes
};
int main(array<System::String ^> ^args)
{
I1^ i1 = gcnew X;
// OK, I1 and I2 have common type: X
// I2^ i3 = static_cast<I2^>(i1); C2440 use safe_cast instead
I2^ i2 = safe_cast<I2^>(i1);
try
{
// fail at runtime, no common type
I3^ i4 = safe_cast<I3^>(i1);
}
catch(InvalidCastException^)
{
Console::WriteLine("Caught expected exception");
}
return 0;
}
Output:
Upcast with safe_cast
An upcast is a cast from a derived type to one of its base classes. This cast is safe and does not require an explicit cast notation. The following sample shows how to perform an upcast with and without safe_cast. You may use the previous project for this example.
// mysafecast.cpp : main project file – safe_cast and upcast.
// compile with: /clr
#include "stdafx.h"
using namespace System;
interface class myA
{
void Test();
};
ref struct myB : public myA
{
virtual void Test()
{
Console::WriteLine("In myB::Test");
}
void Test2()
{
Console::WriteLine("In myB::Test2");
}
};
ref struct myC : public myB
{
virtual void Test() override
{
Console::WriteLine("In C::Test");
};
};
interface class myI {/* more code...*/} ;
value struct myV : public myI {/* more code...*/};
int main(array<System::String ^> ^args)
{
myC ^ cobj = gcnew myC;
// implicit upcast, myC to myB
myB ^ bobj = cobj;
bobj->Test();
bobj->Test2();
// upcast with safe_cast, cobj type to myB
bobj = nullptr;
bobj = safe_cast<myB^>(cobj);
myV ^ pv;
// implicit upcast, myV to myI
myI^ iobj = pv;
// upcast with safe_cast, pv type to myI
iobj = safe_cast<myI^>(pv);
return 0;
}
Output:
---------------------------------------------
Downcast with safe_cast
A downcast is a cast from a base class to a class derived from the base class. A downcast is only safe if the object addressed at runtime is actually addressing a derived class object. Unlike static_cast, safe_cast performs a dynamic check and throws InvalidCastException if the conversion fails. Use the previous project to try the following example.
// mysafecast.cpp : main project file – safe_cast and downcast.
// compile with: /clr
#include "stdafx.h"
using namespace System;
interface class myA { void Test(); };
ref struct myB : public myA
{
virtual void Test() {
Console::WriteLine("In myB::Test()");
}
void Test2() {
Console::WriteLine("In myB::Test2()");
}
};
ref struct myC : public myB
{
virtual void Test() override {
Console::WriteLine("In myC::Test()");
}
};
interface class myI {/* more code */};
value struct myV : public myI {};
int main(array<System::String ^> ^args)
{
myA^ aobj = gcnew myC();
aobj->Test();
myB^ bobj = safe_cast<myB^>(aobj);
bobj->Test();
bobj->Test2();
myV vobj;
myI^ iobj = vobj; // iobj boxes myV
myV^ refv = safe_cast<myV^>(iobj);
Object^ oobj = gcnew myB;
myA^ aobj2= safe_cast<myA^>(oobj);
return 0;
}
Output: