In C++ specific casts are more controlled ways to perform type conversions. These are static_cast
, dynamic_cast
, const_cast
, and reinterpret_cast
.
Each one has its specific purpose
static_cast
: For conversions between related types without polymorphismdynamic_cast
: For safe conversions in polymorphic class hierarchiesconst_cast
: To remove or addconst
(with caution ⚠️)reinterpret_cast
: Low-level conversions between unrelated types, such as pointers and integers (with great caution ❗)
These allow for safer type conversion management compared to the “classic” conversion operator (dataType)object
(especially when working with class hierarchies and inheritance).
Although the (dataType)
operator is quicker to write, it can be dangerous because it does not perform any type of safety checks, which can lead to errors.
Conversion with static_cast
static_cast
is used for conversions between related types that can be determined at compile time. This type of cast is very useful for converting between numeric types, as well as in class hierarchies without polymorphism.
Example with numeric types:
double decimalNumber = 9.99;
int integerNumber = static_cast<int>(decimalNumber); // Explicit conversion
In this case, the double
value is converted to an int
, consciously losing the decimal part of the number. By using static_cast
, a clear conversion is ensured and the programmer is responsible for this loss of precision.
Example with pointers in class hierarchies:
class Base {};
class Derived : public Base {};
Base* basePtr = new Derived();
Derived* derivedPtr = static_cast<Derived*>(basePtr); // Valid conversion
Here, static_cast
converts a base class pointer (Base*
) to a pointer of a derived class (Derived*
). The conversion is valid because basePtr
actually points to an object of the derived class.
static_cast
should not be used when polymorphism is involved, as it does not check if the conversion is safe at runtime. Instead, you should use dynamic_cast
in these cases.
Conversion with dynamic_cast
dynamic_cast
is primarily used when working with class hierarchies that have at least one virtual function (i.e., classes with polymorphism).
This type of cast is the only one that checks the validity of the conversion at runtime, returning nullptr
if the conversion is not possible.
Example of safe conversion in polymorphic hierarchies:
class Base {
public:
virtual void method() {} // Polymorphic class because it has at least one virtual function
};
class Derived : public Base {
public:
void method() override {}
};
Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); // Valid conversion
In this example,
dynamic_cast
correctly converts the pointerbasePtr
(of typeBase*
) to a pointerDerived*
.- If
basePtr
did not point to an object of theDerived
class,dynamic_cast
would returnnullptr
(avoiding errors like illegal memory accesses).
Example of failed conversion:
Base* basePtr = new Base();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr == nullptr) {
std::cout << "The conversion was not successful." << std::endl;
}
Here, the conversion fails because basePtr
does not point to an object of type Derived
, and the pointer derivedPtr
is nullptr
.
- Use
dynamic_cast
when working with classes that have polymorphism and need to verify at runtime if a conversion is valid.
- Do not use
dynamic_cast
if you can determine the conversion at compile time (usestatic_cast
in those cases).
Conversion with const_cast
const_cast
is used to remove or add the const
qualifier to an object.
Although it seems useful, modifying an object that was originally declared as const
can result in undefined behavior, so it should be used with caution.
Example of removing the const qualifier:
const int constantNumber = 42;
int* ptr = const_cast<int*>(&constantNumber); // Removes const
*ptr = 100; // Undefined behavior: modifying a const variable
In this case,
const_cast
is used to remove theconst
qualifier fromconstantNumber
.
Although the compiler allows it, modifying a value declared as const
can lead to unpredictable results.
Example of legitimate use of const_cast:
const_cast
can also be useful when working with functions that do not correctly mark their parameters as const
.
For example, if you have a function from an API that takes a non-const pointer, but you know it does not modify the value:
void functionThatDoesNotModify(int* ptr) {
// Does not modify the value
}
const int number = 50;
functionThatDoesNotModify(const_cast<int*>(&number)); // Legitimate conversion
- Use it only when working with poorly designed functions or APIs that do not properly handle the
const
qualifier.
- Avoid modifying objects that were originally declared as
const
. - Moreover, in general, avoid using it 😉.
Conversion with reinterpret_cast
reinterpret_cast
is the most dangerous cast and is used to perform low-level conversions between pointer types or between pointers and integer data types.
Unlike the other types of casts, it does not perform any type of safety checks and simply “reinterprets” the bits of the object into the new type.
Basically, reinterpret_cast
is similar to the classic cast (dataType)
.
Example of pointer conversion:
int number = 65;
char* letterPtr = reinterpret_cast<char*>(&number);
std::cout << *letterPtr << std::endl; // Prints 'A', which is the ASCII character for 65
In this example:
reinterpret_cast
converts the memory address of the integernumber
into achar*
pointer.- This allows interpreting the first bytes of the binary representation of
number
as an ASCII character.
Example of conversion between pointer and int:
int number = 42;
uintptr_t ptrAsInt = reinterpret_cast<uintptr_t>(&number); // Converts a pointer to an unsigned integer
std::cout << ptrAsInt << std::endl;
Here, the pointer to number
is converted to an unsigned integer of type uintptr_t
(a special type that can store memory addresses). This can be useful when you need to work with memory addresses in a specific context, but in general, it is not recommended to abuse this type of conversions.
- Use it only when working with low-level systems (such as hardware drivers or APIs that require this type of conversions).
- Avoid using it for any type of conversion between classes or data types that are not directly related.
- Moreover, avoid using it a lot.