Language: EN

cpp-static-dynamic-const-cast

Converters static_cast, dynamic_cast and const_cast in C++

In C++ the specific casts are more controlled ways of performing conversions between types. 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 polymorphism
  • dynamic_cast: For safe conversions in polymorphic class hierarchies
  • const_cast: To remove or add const (with caution ⚠️)
  • reinterpret_cast: Low-level conversions between unrelated types, such as pointers and integers (with great caution ❗)

These allow for safer management of type conversion compared to the “classic” conversion operator (dataType)object (especially when working with class hierarchies and inheritance).

Although the operator (dataType) is faster to write, it can be dangerous because it does not perform any type of safety check, which can lead to errors.

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 derived class pointer (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.

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 pointer basePtr (of type Base*) to a Derived* pointer.
  • If basePtr did not point to an object of the Derived class, dynamic_cast would return nullptr (avoiding errors such as illegal memory accesses).

Example with 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.

  • Do not use dynamic_cast if you can determine the conversion at compile time (use static_cast in those cases).

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 lead to 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 the const qualifier from constantNumber.

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
  • Avoid modifying objects that were originally declared as const.
  • Moreover, generally avoid using it 😉.

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 check and simply “reinterprets” the bits of the object in 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 integer number into a char* 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, this type of conversions is not recommended to abuse.

  • Avoid using it for any type of conversion between classes or data types that are not directly related.
  • Moreover, avoid using it a lot.