Chapter 16

Casting

C-style casts, static_cast, dynamic_cast, reinterpret_cast, const_cast, and volatile.

In this chapter

  1. C-Style Casts
  2. C++ Cast Syntax
  3. static_cast
  4. dynamic_cast
  5. reinterpret_cast
  6. const_cast
  7. volatile
1

C-Style Casts

C-style casts force the compiler to treat a value as a different type. They use the familiar parenthesis syntax from C:

int* intArr = (int*) somePointer;  // forces compiler to assume int*
Avoid C-style casts in C++. They are dangerous because they silently apply whichever conversion works — including reinterpret_cast and const_cast — without making it obvious which one. They also can't be searched for easily in a codebase.

2

C++ Cast Syntax

All C++ casts share the same syntax:

ResultType result = cast_operator<ResultType>(objectToCast);
CastUse caseSafety
static_castRelated types, built-in conversionsCompile-time checks only
dynamic_castDowncasting in an inheritance hierarchyRuntime checked — safest for polymorphic types
reinterpret_castCompletely unrelated pointer/type reinterpretationNo checks — most dangerous
const_castAdd or remove const qualifierCareful — removing const on a truly const object is UB

3

static_cast

static_cast performs conversions between related types at compile time. It is used for:

Base* objBase = new Base();

// Downcast: Base* → Derived* (unsafe if objBase doesn't point to a Derived)
Derived* objDer = static_cast<Derived*>(objBase);

// Built-in conversion
double d = static_cast<double>(5);  // 5 → 5.0
static_cast performs compile-time checks only. It is safer than a C-style cast, but it cannot confirm at runtime that a downcast is actually valid. Use dynamic_cast when you need that guarantee.

4

dynamic_cast

dynamic_cast is the opposite of static_cast in terms of timing — it executes at runtime using RTTI (Run-Time Type Information) to verify the cast is valid. It works on pointers and references to polymorphic types (types with at least one virtual function).

Base* objBase = new Derived();

// Runtime check — returns nullptr if the cast is invalid
Derived* objDer = dynamic_cast<Derived*>(objBase);

if (objDer) {
    // Cast succeeded — objBase really did point to a Derived
} else {
    // Cast failed — objBase does not point to a Derived
}
dynamic_cast advantages
  • Runtime-verified — will not silently produce a bad pointer
  • Returns nullptr on failure (pointer) or throws std::bad_cast (reference), which you can handle
  • Converts pointers and references
dynamic_cast disadvantages
  • Runtime cost — RTTI lookup is slower than a static cast
  • Requires at least one virtual function in the hierarchy
  • Does not work on non-polymorphic types

5

reinterpret_cast

reinterpret_cast forces a reinterpretation of the underlying bits — it does not actually change the binary representation of the value, just tells the compiler to treat those bytes as a different type. It can convert:

int x = 65;
char* c = reinterpret_cast<char*>(&x);  // reinterpret the bytes of x as a char*
Use with extreme caution. reinterpret_cast bypasses the type system entirely. It is similar to a C-style cast and provides no safety guarantees. Legitimate uses are rare — mostly hardware register access, serialization, and certain platform-specific tricks.

6

const_cast

const_cast is the only C++ cast that can add or remove the const qualifier from a type. A common use case is calling a non-const function from within a const method when you know it's safe:

void nonConstFunc(int* p) { /* modifies p */ }

void myConstMethod() const {
    // *this is const here, but we need a non-const pointer to _data
    nonConstFunc(const_cast<int*>(_data));
}
Removing const from an object that was originally declared const and then writing to it is undefined behavior. const_cast is only valid when the underlying object is not truly const — for example, a const reference to a non-const variable.
const_cast can also add or remove the volatile qualifier in the same way it handles const.

7

volatile

volatile tells the compiler not to optimize out reads or writes to a variable — every access must actually go to memory. It's used for memory-mapped hardware registers and shared memory in some multi-threaded contexts:

volatile int* hwReg = (volatile int*)0xDEADBEEF;
*hwReg = 1;  // compiler will NOT optimize this away
volatile does not provide thread-safety or atomicity — it only prevents compiler optimization of the access. For multi-threaded synchronization use std::atomic or a mutex instead.
← Chapter 15 ↑ Index Chapter 17 →