C++ Casting | 'static_cast/dynamic_cast' Complete Guide to 4 Types

C++ Casting | 'static_cast/dynamic_cast' Complete Guide to 4 Types

이 글의 핵심

C++ casting complete guide: static_cast, dynamic_cast, const_cast, reinterpret_cast with principles and practical examples.

1. static_cast

Purpose: Compile-time type conversion

Here is the Base implementation:

// Basic type conversion
int x = 10;
double y = static_cast<double>(x);

// Pointer upcasting (safe)
class Base {};
class Derived : public Base {};

Derived* d = new Derived();
Base* b = static_cast<Base*>(d);  // OK

// Pointer downcasting (dangerous)
Base* b2 = new Base();
Derived* d2 = static_cast<Derived*>(b2);  // Compiles but dangerous!

When to Use:

  • Basic type conversion
  • Explicit type conversion
  • Upcasting

2. dynamic_cast

Purpose: Runtime safe downcasting

Here is the derivedMethod implementation:

class Base {
public:
    virtual ~Base() {}  // Virtual function required!
};

class Derived : public Base {
public:
    void derivedMethod() {
        cout << "Derived method" << endl;
    }
};

int main() {
    Base* b = new Derived();
    
    // Safe downcasting
    Derived* d = dynamic_cast<Derived*>(b);
    if (d) {
        d->derivedMethod();  // Success
    } else {
        cout << "Cast failed" << endl;
    }
    
    // Failure example
    Base* b2 = new Base();
    Derived* d2 = dynamic_cast<Derived*>(b2);
    if (!d2) {
        cout << "Cast failed" << endl;  // Printed
    }
}

When to Use:

  • Downcasting
  • Type checking needed
  • RTTI (Run-Time Type Information) required

3. const_cast

Purpose: Remove/add const qualifier

Here is the legacyFunction implementation:

void legacyFunction(char* str) {
    // Legacy function without const
}

void modernFunction(const char* str) {
    // Remove const (dangerous!)
    legacyFunction(const_cast<char*>(str));
}

// Add const
int main() {
    int x = 10;
    const int* ptr = const_cast<const int*>(&x);
}

Warning: Modifying originally const object is undefined behavior!

4. reinterpret_cast

Purpose: Reinterpret pointer as different type

The following example demonstrates the concept in cpp:

int x = 42;
int* ptr = &x;

// Pointer to integer
uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
cout << "Address: " << addr << endl;

// Integer to pointer
int* ptr2 = reinterpret_cast<int*>(addr);

// Pointer type conversion (dangerous!)
double* dptr = reinterpret_cast<double*>(ptr);

When to Use:

  • Low-level programming
  • Hardware access
  • Serialization/deserialization

C-style Cast vs C++ Cast

The following example demonstrates the concept in cpp:

// ❌ C-style (dangerous)
int x = 10;
double y = (double)x;

Base* b = new Derived();
Derived* d = (Derived*)b;  // Unclear which cast

// ✅ C++ style (clear)
double y2 = static_cast<double>(x);
Derived* d2 = dynamic_cast<Derived*>(b);

Practical Examples

Example 1: Polymorphism and dynamic_cast

Here is the draw implementation:

class Shape {
public:
    virtual void draw() = 0;
    virtual ~Shape() {}
};

class Circle : public Shape {
public:
    void draw() override { cout << "Circle" << endl; }
    double getRadius() { return 5.0; }
};

class Rectangle : public Shape {
public:
    void draw() override { cout << "Rectangle" << endl; }
    double getWidth() { return 10.0; }
};

void processShape(Shape* shape) {
    shape->draw();
    
    // Check if Circle
    if (Circle* circle = dynamic_cast<Circle*>(shape)) {
        cout << "Radius: " << circle->getRadius() << endl;
    }
    
    // Check if Rectangle
    if (Rectangle* rect = dynamic_cast<Rectangle*>(shape)) {
        cout << "Width: " << rect->getWidth() << endl;
    }
}

int main() {
    Shape* shapes[] = {
        new Circle(),
        new Rectangle()
    };
    
    for (Shape* shape : shapes) {
        processShape(shape);
        delete shape;
    }
}

Example 2: Serialization

Here is the serialize implementation:

#include <cstring>

struct Data {
    int id;
    double value;
};

void serialize(const Data& data, char* buffer) {
    // Struct to byte array
    memcpy(buffer, &data, sizeof(Data));
}

Data deserialize(const char* buffer) {
    Data data;
    memcpy(&data, buffer, sizeof(Data));
    return data;
}

int main() {
    Data original = {42, 3.14};
    char buffer[sizeof(Data)];
    
    serialize(original, buffer);
    Data restored = deserialize(buffer);
    
    cout << restored.id << ", " << restored.value << endl;
}

Example 3: Plugin System

Here is the execute implementation:

class Plugin {
public:
    virtual void execute() = 0;
    virtual ~Plugin() {}
};

class AudioPlugin : public Plugin {
public:
    void execute() override { cout << "Audio processing" << endl; }
    void setVolume(int v) { volume = v; }
private:
    int volume = 100;
};

void configurePlugin(Plugin* plugin) {
    // Check if AudioPlugin and set volume
    if (AudioPlugin* audio = dynamic_cast<AudioPlugin*>(plugin)) {
        audio->setVolume(80);
    }
}

Common Problems

Problem 1: Ignoring dynamic_cast Failure

The following example demonstrates the concept in cpp:

// ❌ Dangerous
Base* b = new Base();
Derived* d = dynamic_cast<Derived*>(b);
d->derivedMethod();  // Crash! (d is nullptr)

// ✅ Check
if (Derived* d = dynamic_cast<Derived*>(b)) {
    d->derivedMethod();
} else {
    cout << "Cast failed" << endl;
}

Problem 2: const_cast Abuse

The following example demonstrates the concept in cpp:

// ❌ Undefined behavior
const int x = 10;
int* ptr = const_cast<int*>(&x);
*ptr = 20;  // Dangerous!

// ✅ Only when originally non-const
int y = 10;
const int* cptr = &y;
int* ptr2 = const_cast<int*>(cptr);
*ptr2 = 20;  // OK

Problem 3: reinterpret_cast Misuse

The following example demonstrates the concept in cpp:

// ❌ Alignment issue
int x = 42;
double* dptr = reinterpret_cast<double*>(&x);
// *dptr;  // Possible crash!

// ✅ Same size, same alignment
uint32_t u = 42;
int32_t* iptr = reinterpret_cast<int32_t*>(&u);  // OK

Casting Selection Guide

Need type conversion?
├─ Basic type conversion? → static_cast
├─ Downcasting?
│  ├─ Safety needed? → dynamic_cast
│  └─ Performance critical? → static_cast (careful!)
├─ Remove const? → const_cast (careful!)
└─ Pointer reinterpretation? → reinterpret_cast (dangerous!)

Performance Comparison

// static_cast: Compile-time, no overhead
Derived* d1 = static_cast<Derived*>(base);

// dynamic_cast: Runtime type check, slightly slower
Derived* d2 = dynamic_cast<Derived*>(base);

FAQ

Q1: Which cast should I use?

A:

  1. First try without cast
  2. static_cast (most common)
  3. dynamic_cast (downcasting)
  4. const_cast (legacy code)
  5. reinterpret_cast (low-level)

Q2: Why is C-style cast bad?

A:

  • Unclear which cast type
  • Hard to search
  • Unintended conversions possible

Q3: Why is dynamic_cast slow?

A: Uses RTTI to check type at runtime.

Q4: dynamic_cast without virtual function?

A: Impossible. Virtual function needed to enable RTTI.

Q5: How to avoid casting?

A:

  • Use virtual functions
  • Use templates
  • Good design

Q6: Debugging casts?

A:

  • Check dynamic_cast return value
  • Use assert
  • Add logging