nullptr / static_cast / dynamic_cast
C++ provides nullptr for representing null pointer values, and four cast operators (static_cast / dynamic_cast / reinterpret_cast / const_cast) for type conversions. C-style casts ((type)expr) do not make the conversion intent clear at a glance, so C++ encourages using the appropriate named cast operator for each purpose.
Syntax
// ----------------------------------------
// nullptr: null pointer constant (C++11 and later)
// ----------------------------------------
// Use nullptr instead of NULL macro (integer 0)
// nullptr is of type nullptr_t and represents an uninitialized pointer state
int* p = nullptr;
if (p == nullptr) { /* ... */ }
// ----------------------------------------
// static_cast: converts types at compile time
// ----------------------------------------
double d = 3.14;
int i = static_cast<int>(d); // double to int (truncates decimal)
// Downcast: base class pointer to derived class pointer (no runtime type check)
// Base* base = new Derived();
// Derived* derived = static_cast<Derived*>(base);
// ----------------------------------------
// dynamic_cast: converts with runtime type safety check
// ----------------------------------------
// Used for downcasting polymorphic classes (with virtual functions)
// Returns nullptr on failure for pointers; throws std::bad_cast for references
// Base* base = new Derived();
// Derived* derived = dynamic_cast<Derived*>(base);
// if (derived == nullptr) { /* cast failed */ }
// ----------------------------------------
// reinterpret_cast: reinterprets bit representation as another type
// ----------------------------------------
// For pointer-integer conversions and unrelated pointer type conversions
// Limit to low-level programming
// ----------------------------------------
// const_cast: adds or removes const/volatile qualifiers
// ----------------------------------------
// Only safe when the original variable is not const
Cast Operator Reference
| Operator / Keyword | Description |
|---|---|
| nullptr | Type-safe null pointer constant. Represents uninitialized pointer state. Used as a replacement for NULL (integer 0) from C++11 onward. |
| static_cast<T>(expr) | Performs compile-time type conversions. Used for numeric type conversions and pointer conversions between base and derived classes. No runtime type check. |
| dynamic_cast<T>(expr) | Performs downcasting with runtime type safety check. Only usable with polymorphic classes (those with virtual functions). Returns nullptr on failure for pointers, throws std::bad_cast for references. |
| reinterpret_cast<T>(expr) | Forcibly reinterprets the bit representation as another type. Used for pointer-integer conversions and low-level type conversions. Use locations should be minimized. |
| const_cast<T>(expr) | Adds or removes const or volatile qualifiers. This is the only cast that can modify const. Only safe for writing when the original variable is not const. |
Sample Code
dragon_ball_cast.cpp
#include <iostream>
#include <string>
#include <vector>
// Base class: Fighter (has virtual functions, so dynamic_cast is usable)
class Fighter {
protected:
std::string name;
int powerLevel;
public:
Fighter(const std::string& name, int powerLevel)
: name(name), powerLevel(powerLevel) {}
virtual void showInfo() const {
std::cout << "[Fighter] " << name
<< " Power: " << powerLevel << std::endl;
}
std::string getName() const { return name; }
int getPowerLevel() const { return powerLevel; }
virtual ~Fighter() {}
};
// Derived class: SaiyanWarrior
class SaiyanWarrior : public Fighter {
private:
int superSaiyanLevel;
public:
SaiyanWarrior(const std::string& name, int powerLevel, int ssLevel)
: Fighter(name, powerLevel), superSaiyanLevel(ssLevel) {}
void showInfo() const override {
std::cout << "[Saiyan] " << name
<< " Power: " << powerLevel;
if (superSaiyanLevel > 0) {
std::cout << " Super Saiyan Level: " << superSaiyanLevel;
}
std::cout << std::endl;
}
void transformSSJ() {
superSaiyanLevel++;
powerLevel *= 50;
std::cout << name << " transformed to Super Saiyan Level "
<< superSaiyanLevel << "! Power: " << powerLevel << std::endl;
}
int getSuperSaiyanLevel() const { return superSaiyanLevel; }
};
// Derived class: NamekianWarrior
class NamekianWarrior : public Fighter {
private:
int regenerationCount;
public:
NamekianWarrior(const std::string& name, int powerLevel)
: Fighter(name, powerLevel), regenerationCount(0) {}
void showInfo() const override {
std::cout << "[Namekian] " << name
<< " Power: " << powerLevel
<< " Regenerations: " << regenerationCount << std::endl;
}
void regenerate() {
regenerationCount++;
std::cout << name << " regenerated ("
<< regenerationCount << " times)" << std::endl;
}
};
// Demo: nullptr
void demoNullptr() {
std::cout << "=== nullptr Demo ===" << std::endl;
// Initialize with nullptr (represents uninitialized pointer state)
Fighter* goku = nullptr;
Fighter* vegeta = nullptr;
if (goku == nullptr) {
std::cout << "Son Goku has not appeared yet (nullptr)" << std::endl;
}
goku = new SaiyanWarrior("Son Goku", 90000, 0);
vegeta = new SaiyanWarrior("Vegeta", 18000, 0);
goku->showInfo();
vegeta->showInfo();
delete goku;
delete vegeta;
goku = nullptr;
vegeta = nullptr;
std::cout << std::endl;
}
// Demo: static_cast
void demoStaticCast() {
std::cout << "=== static_cast Demo ===" << std::endl;
double freezaPowerDouble = 530000.75;
int freezaPowerInt = static_cast<int>(freezaPowerDouble);
std::cout << "Frieza power (double): " << freezaPowerDouble << std::endl;
std::cout << "Frieza power (int): " << freezaPowerInt << std::endl;
SaiyanWarrior* gohan = new SaiyanWarrior("Son Gohan", 200000, 2);
Fighter* fighterPtr = static_cast<Fighter*>(gohan);
fighterPtr->showInfo();
SaiyanWarrior* gohanBack = static_cast<SaiyanWarrior*>(fighterPtr);
std::cout << gohanBack->getName() << " Super Saiyan Level: "
<< gohanBack->getSuperSaiyanLevel() << std::endl;
delete gohan;
std::cout << std::endl;
}
// Demo: dynamic_cast
void demoDynamicCast() {
std::cout << "=== dynamic_cast Demo ===" << std::endl;
std::vector<Fighter*> fighters;
fighters.push_back(new SaiyanWarrior("Son Goku", 3000000, 3));
fighters.push_back(new NamekianWarrior("Piccolo", 800000));
fighters.push_back(new SaiyanWarrior("Vegeta", 2500000, 2));
for (int i = 0; i < (int)fighters.size(); i++) {
fighters[i]->showInfo();
SaiyanWarrior* saiyajin = dynamic_cast<SaiyanWarrior*>(fighters[i]);
if (saiyajin != nullptr) {
saiyajin->transformSSJ();
}
NamekianWarrior* namekian = dynamic_cast<NamekianWarrior*>(fighters[i]);
if (namekian != nullptr) {
namekian->regenerate();
}
}
for (int i = 0; i < (int)fighters.size(); i++) {
delete fighters[i];
fighters[i] = nullptr;
}
std::cout << std::endl;
}
// Demo: reinterpret_cast
void demoReinterpretCast() {
std::cout << "=== reinterpret_cast Demo ===" << std::endl;
SaiyanWarrior trunks("Trunks", 5000000, 2);
uintptr_t address = reinterpret_cast<uintptr_t>(&trunks);
std::cout << "Trunks memory address (decimal): " << address << std::endl;
SaiyanWarrior* trunksPtr = reinterpret_cast<SaiyanWarrior*>(address);
std::cout << "Name retrieved from restored pointer: " << trunksPtr->getName() << std::endl;
std::cout << std::endl;
}
int main() {
demoNullptr();
demoStaticCast();
demoDynamicCast();
demoReinterpretCast();
return 0;
}
g++ -std=c++11 dragon_ball_cast.cpp -o dragon_ball_cast ./dragon_ball_cast === nullptr Demo === Son Goku has not appeared yet (nullptr) [Saiyan] Son Goku Power: 90000 [Saiyan] Vegeta Power: 18000 === static_cast Demo === Frieza power (double): 530000.75 Frieza power (int): 530000 [Saiyan] Son Gohan Power: 200000 Super Saiyan Level: 2 Son Gohan Super Saiyan Level: 2 === dynamic_cast Demo === [Saiyan] Son Goku Power: 3000000 Super Saiyan Level: 3 Son Goku transformed to Super Saiyan Level 4! Power: 150000000 [Namekian] Piccolo Power: 800000 Regenerations: 0 Piccolo regenerated (1 times) [Saiyan] Vegeta Power: 2500000 Super Saiyan Level: 2 Vegeta transformed to Super Saiyan Level 3! Power: 125000000 === reinterpret_cast Demo === Trunks memory address (decimal): 140732091613664 Name retrieved from restored pointer: Trunks
Common mistake 1: Passing NULL to an overloaded function and getting the integer version
NULL is actually integer 0, so when passed to a function with both pointer and integer overloads, the integer version may be called. Using nullptr avoids this since it is of type nullptr_t.
// NG: NULL may select the integer overload
void process(int val) { std::cout << "Integer version: " << val << std::endl; }
void process(Fighter* p) { std::cout << "Pointer version" << std::endl; }
process(NULL); // Integer version is called
OK: Using nullptr correctly selects the pointer version.
// OK: nullptr selects the pointer version process(nullptr); // Pointer version is called
Common mistake 2: Using static_cast for a downcast without verifying the actual type
static_cast does not perform a runtime type check for downcasts. Casting to an incorrect derived type results in undefined behavior. When the type is uncertain, use dynamic_cast with a nullptr check.
// NG: static_cast with incorrect actual type causes undefined behavior
Fighter* krillin = new NamekianWarrior("Krillin", 75000);
SaiyanWarrior* wrongCast = static_cast<SaiyanWarrior*>(krillin); // Undefined behavior
OK: Use dynamic_cast for safe type checking.
// OK: Use dynamic_cast with nullptr check
SaiyanWarrior* saiyajin = dynamic_cast<SaiyanWarrior*>(krillin);
if (saiyajin != nullptr) {
saiyajin->transformSSJ();
} else {
std::cout << "Not a Saiyan" << std::endl;
}
Overview
nullptr is the null pointer constant introduced in C++11, replacing integer 0 and the NULL macro. Since nullptr is of type nullptr_t, it can represent an uninitialized pointer state without integer confusion, and overload resolution will not accidentally select an integer function. Using the appropriate cast operator for each purpose is important. static_cast is for compile-time conversions (numeric types, upcasts, downcasts where the type is certain); dynamic_cast is for safe downcasting of polymorphic types with runtime type checking; reinterpret_cast is for forced bit-level reinterpretation and should be limited to low-level programming. const_cast specializes in adding/removing const qualifiers — the other three casts cannot modify const. Using C-style casts like (type)expr can silently combine multiple casts and obscure intent; using C++ named casts makes the intent explicit and improves code review and debugging.
If you find any errors or copyright issues, please contact us.