Four specific type conversion mechanisms in C++

In C++, there are four specific type conversion mechanisms, namely:

  1. static_cast (static conversion, executed at compile time)
    static_cast can perform type conversion on objects, but this conversion is determined at compile time. It applies to the following situations:

    1. Pointer type from pointer type to base type.
    2. From a pointer type of a base type to a pointer type of a derived class (but without runtime type checking, potentially unsafe).
    3. Between numeric types (such as int to float, float to int, etc.).
    4. From enumeration types to integers or floating point numbers.
    5. From integers or floating point numbers to enumeration types.
      However, for conversions involving objects within a class hierarchy, especially when polymorphism is involved, it is generally safer to use dynamic_cast (this requires virtual function support to check at runtime type).
      However, if you are sure that the conversion is legal and safe, you can use static_cast to convert between class objects, but this usually requires a deep understanding of the structure of the code.
    • Purpose: This is the most commonly used conversion method, used for conversion between various common types, such as integer to floating point number, floating point number to integer, etc.
    • Example:
      float f = 3.14;
      int i = static_cast<int>(f);
      
    • Note: static_cast cannot convert all CV qualifiers (const and volatile) of the type.
  2. dynamic_cast

    • Purpose: Mainly used for conversion between polymorphic types. When performing a downcast (from a base class to a derived class), dynamic_cast checks at runtime whether the conversion is legal. If the conversion is illegal, for pointer types, it will return nullptr; for reference types, it will throw a bad_cast exception.
      dynamic_cast is mainly used to handle the conversion of objects in a class hierarchy with virtual functions. It is mainly used in the following situations:
    1. Pointer conversion:
      • From base class pointer type to derived class pointer type.
      • From derived class pointer type to base class pointer type.
        Note: The base class here should usually contain at least one virtual function.
    2. Quote conversion:
      • From a base class reference type to a derived class reference type.
      • If the cast fails (for example, the actual object is not the expected derived type), a std::bad_cast exception is thrown.
    3. Conversions related to virtual base classes: In cases where virtual inheritance is involved, dynamic_cast can be used to perform appropriate conversions within the class hierarchy.
      The main advantage of dynamic_cast is that it checks the validity of the conversion at runtime. If the conversion is illegal or unsafe, it will return nullptr for pointer types; it will throw an exception for reference types.
      For example, consider the following scenario, where Derived is a derived class of Base:
Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr) {<!-- -->
// Conversion successful, derivedPtr can be used safely
} else {<!-- -->
// Conversion failed
}
Here, using `dynamic_cast` ensures that the conversion is safe and allows the runtime to check the validity of the conversion.

   - example:
Base* bPtr = new Derived();
Derived* dPtr = dynamic_cast<Derived*>(bPtr);
 - Note: Using `dynamic_cast` requires a virtual function in the base class so that runtime type information can be checked.

3. **`const_cast (constant conversion, executed at compile time)`**
   - Purpose: Const or volatile qualifier used to convert types. However, modifying the value after removing `const` through `const_cast` is undefined behavior, unless the memory itself is non-`const`. (Even if the conversion is used, the conversion is only performed at the compiler's rule level, but no changes are sent at other levels (for example, the hardware level uses ro memory. At this time, although the const is converted, it will still be modified when it is modified. An unpredictable error occurred))

const_cast is mainly used to modify the const or volatile qualification of variables. It is one of four C++ casts and is used in the following situations:

  1. Remove const qualification:

    • A pointer to a const object can be converted to a pointer to a non-const object.
    • You can convert a reference to a const object into a reference to a non-const object.
      For example:
    const int val = 10;
    int* ptr = const_cast<int*>( & amp;val);
    
  2. Remove volatile qualification:

    • A pointer to a volatile object can be converted to a pointer to a non-volatile object.
    • You can convert a reference to a volatile object into a reference to a non-volatile object.
  3. Add const or volatile qualification:

    • Although this is uncommon, const_cast can also be used to convert a pointer or reference to a non-const object to const or volatile .

It should be noted that although const_cast can be used to remove the const qualification of an object, if the object was originally defined in the const way, Then trying to modify its value is undefined behavior. In other words, const_cast gives you the ability to change the value of an object, but it doesn’t mean you always have permission to do so.
explain:
const_cast is used to modify the const or volatile attribute of a type. However, using const_cast to remove the const qualification and modify its value is dangerous, especially for objects that are originally const. This operation may result in undefined behavior.

“Undefined Behavior” (UB for short) is a proper noun in C++, which means that when a program exhibits such behavior, the standard does not define specific behavior or results. In short, the program may do anything in this case: it may work normally, it may crash, it may produce incorrect output, etc.
Consider the following example:

const int x = 10;
int* px = const_cast<int*>( & amp;x);
*px = 20; // Undefined behavior!

Here, we declare a const int variable x. Then we use const_cast to remove its constness and try to modify its value. This is undefined behavior.
The reason is that a const object may be stored in read-only memory (e.g., a text or code segment on some hardware architectures), and trying to modify it may cause the program to crash. Even if it doesn’t crash, since the compiler knows that x is a constant, it may have done some optimization so that even if we try to change its value, the actual value may not change.

In short, const_cast is a powerful but dangerous tool. When you decide to use it, make sure you know what you are doing and avoid modifying the value of an object that is originally const.

  • Example:
    const int a = 10;
    int* pa = const_cast<int*>( & amp;a);
    
  • Note: const_cast is only used to adjust the type qualifier of an object.
  1. reinterpret_cast (reinterpret type conversion)

    • Purpose: Used for low-level type conversion, which can convert any type of pointer to any other type of pointer, or any pointer to an integer type, and vice versa. The result is platform dependent and rarely used directly.
      reinterpret_cast is one of four type casts provided by C++ and performs low-level, machine-dependent conversions. Because of how powerful and dangerous it is, extreme caution is required when using it. The following are several situations where reinterpret_cast is applicable:
  2. Conversion between pointers and integers:
    You can convert a pointer to a sufficiently large integer type, or an integer to a pointer.

    int* ptr = new int(10);
    std::uintptr_t intVal = reinterpret_cast<std::uintptr_t>(ptr);
    int* newPtr = reinterpret_cast<int*>(intVal);
    
  3. Conversion between pointers of different types:
    For example, converting from a void* pointer to a pointer of another type, or converting a pointer of one type to a completely different type.

    double value = 3.14;
    double* dptr = & amp;value;
    void* vptr = reinterpret_cast<void*>(dptr);
    double* newDptr = reinterpret_cast<double*>(vptr);
    
  4. Conversion between different types of references:
    This can be used to cast a reference of one type to a different type.

    int x = 10;
    double & amp; ref = reinterpret_cast<double & amp;>(x);
    
  5. Conversion between pointers to class members:
    You can convert a pointer to a data member of one class to a pointer to a data member of another class, or you can convert a pointer to a member function of one class to a pointer to a member function of another class.

  6. Conversion between pointer and member pointer:
    In some cases, it may be necessary to convert between regular pointers and member pointers of a class.

It should be noted that the conversion provided by reinterpret_cast is a simple byte interpretation by the compiler and does not involve runtime type information or checking. Therefore, while it is very flexible, it is also prone to undefined behavior. Therefore, try to avoid using it unless you know exactly what you are doing.

  • Example:
    int i = 42;
    int* p = & amp;i;
    long address = reinterpret_cast<long>(p);
    
  • Note: reinterpret_cast is very dangerous and should be used only when the meaning and consequences of the conversion are clear.

In short, each type conversion operator has its specific application scenarios. When performing type conversion, the most appropriate conversion mechanism should be selected to ensure the correctness and readability of the code.

Summary:
static_cast: used for conversion between floating point and integer
dynamic_cast: used for polymorphic conversion between classes
const_cast: used to convert CV qualifiers (only a rule-based conversion, that is, no error will be reported when modifying the const value after conversion)
reinterpret_cast: used for pointer type conversion, which can convert a pointer to any type of pointer (very dangerous, so pay special attention when converting)

Application scenarios of dynamic_cast conversion:
dynamic_cast is mainly used to safely perform downward or side casts in the inheritance hierarchy of a class. It checks at runtime whether the cast is legal. If illegal, it will return nullptr for pointer type conversion, and throw exception std::bad_cast for reference type conversion.

Application scenario: Imagine a graphics system with a base class Shape, from which Circle and Rectangle are derived. >. Now you have a pointer to a Shape and want to determine whether it points to a Circle object so that you can call a Circle-specific method.

class Shape {<!-- -->
public:
    virtual ~Shape() {<!-- -->}
    // ... other members ...
};

class Circle : public Shape {<!-- -->
public:
    void drawCircle() {<!-- -->
        // draw a circle
    }
    // ... other members ...
};

class Rectangle : public Shape {<!-- -->
public:
    void drawRectangle() {<!-- -->
        // draw a rectangle
    }
    // ... other members ...
};

void Draw(Shape* shape) {<!-- -->
    Circle* circle = dynamic_cast<Circle*>(shape);
    if(circle) {<!-- -->
        circle->drawCircle();
    } else {<!-- -->
        Rectangle* rect = dynamic_cast<Rectangle*>(shape);
        if(rect) {<!-- -->
            rect->drawRectangle();
        }
    }
}

In the above code, we can safely check the actual type pointed to by shape and perform specific operations accordingly.

Note: For dynamic_cast to work, the source type of the cast must contain virtual functions, making it polymorphic. This is why we have a virtual destructor in the Shape class.