Language: EN

cpp-static-dynamic-const-cast

Converters static_cast, dynamic_cast and const_cast in C++

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

  • Do not use dynamic_cast if you can determine the conversion at compile time (use static_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 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, 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 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, it is not recommended to abuse 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.