Module 06 Tutorial
Prerequisites: Review Module 06 Concepts first.
This module introduces C++ type casting operators. You’ll learn when to use each cast type through practical exercises: converting scalar types, serializing pointers, and identifying types at runtime.
Exercise 00: Conversion of Scalar Types
Section titled “Exercise 00: Conversion of Scalar Types”Subject Analysis
Section titled “Subject Analysis”Build a ScalarConverter class with a static method that:
- Takes a string literal representing a C++ literal value
- Detects what type the literal is (char, int, float, double)
- Converts and displays the value in all four scalar types
- Handles special values:
nan,nanf,inf,inff,+inf,-inf, etc.
Key constraints:
- Class must be non-instantiable (only static method)
- Must handle edge cases: overflow, non-displayable chars
- Display format:
.0suffix for whole float/double values
Expected output format:
char: 'a'int: 97float: 97.0fdouble: 97.0Approach Strategy
Section titled “Approach Strategy”Think before coding:
-
What types of input can we receive?
- Single character:
'a','*' - Integer:
42,-17,0 - Float:
42.0f,-4.2f,nanf,inff - Double:
42.0,-4.2,nan,inf
- Single character:
-
How to detect the type?
- char: single non-digit character
- int: all digits with optional leading sign
- float: has ‘f’ suffix (or special: nanf, inff)
- double: has decimal point without ‘f’ (or special: nan, inf)
-
Order of detection matters:
1. Special values first (nan, inf variants)2. Single char (but not a digit)3. Integer (all digits)4. Float (ends with 'f')5. Double (has decimal) -
What can go wrong in conversion?
- int overflow → “impossible”
- char out of range (0-127) → “impossible”
- non-printable char → “Non displayable”
- inf/nan → some conversions impossible
Progressive Code Building
Section titled “Progressive Code Building”Stage 1: Class structure (non-instantiable)
#ifndef SCALARCONVERTER_HPP#define SCALARCONVERTER_HPP
#include <string>
class ScalarConverter {private: // Private constructor = cannot instantiate ScalarConverter(); ScalarConverter(const ScalarConverter& other); ScalarConverter& operator=(const ScalarConverter& other); ~ScalarConverter();
public: static void convert(const std::string& literal);};
#endifStage 2: Type detection helpers
#include "ScalarConverter.hpp"#include <cstdlib> // strtod, strtol#include <cctype> // isdigit, isprint#include <climits> // INT_MIN, INT_MAX#include <cmath> // isnan, isinf#include <iostream>#include <iomanip> // setprecision
// Private helpers (in anonymous namespace or as private static)static bool isSpecialFloat(const std::string& s) { return s == "nanf" || s == "inff" || s == "+inff" || s == "-inff";}
static bool isSpecialDouble(const std::string& s) { return s == "nan" || s == "inf" || s == "+inf" || s == "-inf";}
static bool isChar(const std::string& s) { // Single character that is NOT a digit return s.length() == 1 && !std::isdigit(s[0]);}
static bool isInt(const std::string& s) { if (s.empty()) return false; size_t start = 0; if (s[0] == '+' || s[0] == '-') start = 1; if (start >= s.length()) return false;
for (size_t i = start; i < s.length(); i++) { if (!std::isdigit(s[i])) return false; } return true;}
static bool isFloat(const std::string& s) { if (s.empty() || s[s.length() - 1] != 'f') return false; std::string withoutF = s.substr(0, s.length() - 1); char* end; std::strtod(withoutF.c_str(), &end); return *end == '\0' && withoutF.find('.') != std::string::npos;}
static bool isDouble(const std::string& s) { if (s.empty()) return false; char* end; std::strtod(s.c_str(), &end); return *end == '\0' && s.find('.') != std::string::npos;}Stage 3: Conversion and display
static void printChar(double d) { if (std::isnan(d) || std::isinf(d) || d < 0 || d > 127) std::cout << "char: impossible" << std::endl; else if (!std::isprint(static_cast<int>(d))) std::cout << "char: Non displayable" << std::endl; else std::cout << "char: '" << static_cast<char>(d) << "'" << std::endl;}
static void printInt(double d) { if (std::isnan(d) || std::isinf(d) || d < INT_MIN || d > INT_MAX) std::cout << "int: impossible" << std::endl; else std::cout << "int: " << static_cast<int>(d) << std::endl;}
static void printFloatDouble(double d) { std::cout << std::fixed << std::setprecision(1); std::cout << "float: " << static_cast<float>(d) << "f" << std::endl; std::cout << "double: " << d << std::endl;}
static void fromDouble(double d) { printChar(d); printInt(d); printFloatDouble(d);}Stage 4: Main convert function
void ScalarConverter::convert(const std::string& literal) { // Handle special float values if (isSpecialFloat(literal)) { std::string withoutF = literal.substr(0, literal.length() - 1); double d = std::strtod(withoutF.c_str(), NULL); fromDouble(d); return; }
// Handle special double values if (isSpecialDouble(literal)) { double d = std::strtod(literal.c_str(), NULL); fromDouble(d); return; }
// Handle char if (isChar(literal)) { fromDouble(static_cast<double>(literal[0])); return; }
// Handle int if (isInt(literal)) { long l = std::strtol(literal.c_str(), NULL, 10); if (l < INT_MIN || l > INT_MAX) { std::cout << "char: impossible" << std::endl; std::cout << "int: impossible" << std::endl; std::cout << "float: impossible" << std::endl; std::cout << "double: impossible" << std::endl; return; } fromDouble(static_cast<double>(l)); return; }
// Handle float if (isFloat(literal)) { std::string withoutF = literal.substr(0, literal.length() - 1); double d = std::strtod(withoutF.c_str(), NULL); fromDouble(d); return; }
// Handle double if (isDouble(literal)) { double d = std::strtod(literal.c_str(), NULL); fromDouble(d); return; }
// Invalid input std::cout << "char: impossible" << std::endl; std::cout << "int: impossible" << std::endl; std::cout << "float: impossible" << std::endl; std::cout << "double: impossible" << std::endl;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”Type detection logic:
| Function | Logic | Why This Way |
|---|---|---|
isChar() | Length 1, not a digit | Digit chars like ‘5’ would be detected as int |
isInt() | All digits, optional sign | Must reject strings with decimal points |
isFloat() | Ends with ‘f’, valid number, has ’.’ | Distinguishes from int and double |
isDouble() | Valid number, has ’.’, no ‘f’ suffix | Fallback numeric type |
Conversion strategy:
| From Type | Strategy |
|---|---|
| char | Cast to double, then to all types |
| int | Cast to double first for range checking |
| float/double | Parse with strtod, display all |
| special | Parse directly (nan/inf handling built-in) |
Why cast everything to double first?
// double has enough precision to hold all other types// Makes range checking uniformdouble d = /* input */;printChar(d); // Check 0-127 rangeprintInt(d); // Check INT_MIN-INT_MAXprintFloatDouble(d); // Always worksCommon Pitfalls
Section titled “Common Pitfalls”1. Wrong detection order:
// WRONG: "42" detected as double before intif (isDouble(s)) ... // Matches "42"!
// RIGHT: Check int before doubleif (isInt(s)) ...if (isDouble(s)) ...2. Missing precision format:
// WRONG: Outputs "42f" and "42"std::cout << "float: " << 42.0f << "f" << std::endl;
// RIGHT: Outputs "42.0f" and "42.0"std::cout << std::fixed << std::setprecision(1);std::cout << "float: " << 42.0f << "f" << std::endl;3. Forgetting edge cases:
// WRONG: Crashes on empty stringif (s[0] == '-') ...
// RIGHT: Check length firstif (!s.empty() && s[0] == '-') ...4. Not handling sign-only strings:
// "-" and "+" are not valid numbersstatic bool isInt(const std::string& s) { if (s.empty()) return false; size_t start = 0; if (s[0] == '+' || s[0] == '-') start = 1; if (start >= s.length()) return false; // Just sign, no digits // ...}Testing Tips
Section titled “Testing Tips”Test script:
# Basic types./convert 'a' # char./convert 42 # int./convert 42.0f # float./convert 42.0 # double
# Edge cases./convert 0 # Zero./convert -42 # Negative./convert 127 # Max char./convert 128 # Char impossible
# Special values./convert nan./convert nanf./convert inf./convert inff./convert +inf./convert -inff
# Non-displayable./convert 0 # Non displayable (null char)./convert 31 # Non displayable (control char)
# Invalid./convert "" # Empty./convert "hello" # Invalid literal./convert "42.42.42" # Multiple decimalsExpected outputs:
$ ./convert 'a'char: 'a'int: 97float: 97.0fdouble: 97.0
$ ./convert nanchar: impossibleint: impossiblefloat: nanfdouble: nan
$ ./convert 128char: impossibleint: 128float: 128.0fdouble: 128.0Final Code
Section titled “Final Code”#ifndef SCALARCONVERTER_HPP#define SCALARCONVERTER_HPP
#include <string>
class ScalarConverter {private: ScalarConverter(); ScalarConverter(const ScalarConverter& other); ScalarConverter& operator=(const ScalarConverter& other); ~ScalarConverter();
public: static void convert(const std::string& literal);};
#endif#include "ScalarConverter.hpp"#include <cstdlib>#include <cctype>#include <climits>#include <cmath>#include <iostream>#include <iomanip>
// OCF - never used but required for non-instantiable classScalarConverter::ScalarConverter() {}ScalarConverter::ScalarConverter(const ScalarConverter& other) { (void)other; }ScalarConverter& ScalarConverter::operator=(const ScalarConverter& other) { (void)other; return *this; }ScalarConverter::~ScalarConverter() {}
// Type detection helpersstatic bool isSpecialFloat(const std::string& s) { return s == "nanf" || s == "inff" || s == "+inff" || s == "-inff";}
static bool isSpecialDouble(const std::string& s) { return s == "nan" || s == "inf" || s == "+inf" || s == "-inf";}
static bool isChar(const std::string& s) { return s.length() == 1 && !std::isdigit(s[0]);}
static bool isInt(const std::string& s) { if (s.empty()) return false; size_t start = 0; if (s[0] == '+' || s[0] == '-') start = 1; if (start >= s.length()) return false; for (size_t i = start; i < s.length(); i++) { if (!std::isdigit(s[i])) return false; } return true;}
static bool isFloat(const std::string& s) { if (s.empty() || s[s.length() - 1] != 'f') return false; std::string withoutF = s.substr(0, s.length() - 1); char* end; std::strtod(withoutF.c_str(), &end); return *end == '\0' && withoutF.find('.') != std::string::npos;}
static bool isDouble(const std::string& s) { if (s.empty()) return false; char* end; std::strtod(s.c_str(), &end); return *end == '\0' && s.find('.') != std::string::npos;}
// Conversion helpersstatic void printChar(double d) { if (std::isnan(d) || std::isinf(d) || d < 0 || d > 127) std::cout << "char: impossible" << std::endl; else if (!std::isprint(static_cast<int>(d))) std::cout << "char: Non displayable" << std::endl; else std::cout << "char: '" << static_cast<char>(d) << "'" << std::endl;}
static void printInt(double d) { if (std::isnan(d) || std::isinf(d) || d < INT_MIN || d > INT_MAX) std::cout << "int: impossible" << std::endl; else std::cout << "int: " << static_cast<int>(d) << std::endl;}
static void printFloatDouble(double d) { std::cout << std::fixed << std::setprecision(1); std::cout << "float: " << static_cast<float>(d) << "f" << std::endl; std::cout << "double: " << d << std::endl;}
static void fromDouble(double d) { printChar(d); printInt(d); printFloatDouble(d);}
void ScalarConverter::convert(const std::string& literal) { if (isSpecialFloat(literal)) { std::string withoutF = literal.substr(0, literal.length() - 1); fromDouble(std::strtod(withoutF.c_str(), NULL)); return; } if (isSpecialDouble(literal)) { fromDouble(std::strtod(literal.c_str(), NULL)); return; } if (isChar(literal)) { fromDouble(static_cast<double>(literal[0])); return; } if (isInt(literal)) { long l = std::strtol(literal.c_str(), NULL, 10); if (l < INT_MIN || l > INT_MAX) { std::cout << "char: impossible" << std::endl; std::cout << "int: impossible" << std::endl; std::cout << "float: impossible" << std::endl; std::cout << "double: impossible" << std::endl; return; } fromDouble(static_cast<double>(l)); return; } if (isFloat(literal)) { std::string withoutF = literal.substr(0, literal.length() - 1); fromDouble(std::strtod(withoutF.c_str(), NULL)); return; } if (isDouble(literal)) { fromDouble(std::strtod(literal.c_str(), NULL)); return; } std::cout << "char: impossible" << std::endl; std::cout << "int: impossible" << std::endl; std::cout << "float: impossible" << std::endl; std::cout << "double: impossible" << std::endl;}#include "ScalarConverter.hpp"#include <iostream>
int main(int argc, char** argv) { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " <literal>" << std::endl; return 1; } ScalarConverter::convert(argv[1]); return 0;}Exercise 01: Serialization
Section titled “Exercise 01: Serialization”Subject Analysis
Section titled “Subject Analysis”Build a Serializer class that:
- Converts a
Data*pointer touintptr_t(serialize) - Converts a
uintptr_tback toData*(deserialize) - Must be non-instantiable (static methods only)
- You must create a non-empty
Datastruct
Key insight: This exercise teaches reinterpret_cast - the most dangerous cast that reinterprets the bit pattern.
Approach Strategy
Section titled “Approach Strategy”Think before coding:
-
What is uintptr_t?
- An unsigned integer type guaranteed to hold any pointer
- Defined in
<cstdint>(C++11) or<stdint.h>(C99) - For C++98: use
<stdint.h>or cast tounsigned long
-
Why reinterpret_cast?
- Pointer ↔ integer is not a type conversion
- It’s a bit-level reinterpretation
static_castwon’t work for this
-
What to test?
- Round-trip:
deserialize(serialize(ptr)) == ptr - Data integrity: values in struct unchanged
- Round-trip:
Progressive Code Building
Section titled “Progressive Code Building”Stage 1: Data structure
#ifndef DATA_HPP#define DATA_HPP
#include <string>
struct Data { int id; std::string name; double value;};
#endifStage 2: Serializer class
#ifndef SERIALIZER_HPP#define SERIALIZER_HPP
#include <stdint.h> // uintptr_t (C99/C++98 compatible)#include "Data.hpp"
class Serializer {private: Serializer(); Serializer(const Serializer& other); Serializer& operator=(const Serializer& other); ~Serializer();
public: static uintptr_t serialize(Data* ptr); static Data* deserialize(uintptr_t raw);};
#endifStage 3: Implementation
#include "Serializer.hpp"
Serializer::Serializer() {}Serializer::Serializer(const Serializer& other) { (void)other; }Serializer& Serializer::operator=(const Serializer& other) { (void)other; return *this; }Serializer::~Serializer() {}
uintptr_t Serializer::serialize(Data* ptr) { return reinterpret_cast<uintptr_t>(ptr);}
Data* Serializer::deserialize(uintptr_t raw) { return reinterpret_cast<Data*>(raw);}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”The core operations:
| Operation | Code | What Happens |
|---|---|---|
| Serialize | reinterpret_cast<uintptr_t>(ptr) | Takes the memory address bits, treats them as an integer |
| Deserialize | reinterpret_cast<Data*>(raw) | Takes integer bits, treats them as a memory address |
Why reinterpret_cast is required:
// static_cast CANNOT do this - compilation error:uintptr_t bad = static_cast<uintptr_t>(ptr); // ERROR!
// reinterpret_cast is the ONLY cast that can:uintptr_t good = reinterpret_cast<uintptr_t>(ptr); // OKMemory visualization:
Data object at address 0x7fff5fbff8a0:┌─────────────────────────────┐│ id: 42 ││ name: "test" ││ value: 3.14 │└─────────────────────────────┘
serialize(&data) → 0x7fff5fbff8a0 (as integer)deserialize(0x7fff5fbff8a0) → pointer to same objectCommon Pitfalls
Section titled “Common Pitfalls”1. Empty Data struct:
// WRONG: Subject requires non-empty Datastruct Data {};
// RIGHT: Add some membersstruct Data { int value; std::string str;};2. Not testing round-trip:
// WRONG: Only testing one directionuintptr_t raw = Serializer::serialize(&d);std::cout << raw << std::endl; // Proves nothing!
// RIGHT: Complete round-trip verificationData* result = Serializer::deserialize(Serializer::serialize(&d));if (result == &d) std::cout << "Success: same address!" << std::endl;3. Using C++11 header:
// May not work in strict C++98#include <cstdint>
// Use C99 header instead (works in C++98)#include <stdint.h>Testing Tips
Section titled “Testing Tips”#include "Serializer.hpp"#include <iostream>
int main() { // Create and populate Data Data original; original.id = 42; original.name = "test_data"; original.value = 3.14159;
std::cout << "Original address: " << &original << std::endl; std::cout << "Original values: id=" << original.id << ", name=" << original.name << ", value=" << original.value << std::endl;
// Serialize uintptr_t raw = Serializer::serialize(&original); std::cout << "Serialized: " << raw << std::endl;
// Deserialize Data* recovered = Serializer::deserialize(raw); std::cout << "Recovered address: " << recovered << std::endl;
// Verify pointer equality if (recovered == &original) std::cout << "✓ Pointer comparison: SAME ADDRESS" << std::endl; else std::cout << "✗ Pointer comparison: DIFFERENT ADDRESS" << std::endl;
// Verify data integrity std::cout << "Recovered values: id=" << recovered->id << ", name=" << recovered->name << ", value=" << recovered->value << std::endl;
return 0;}Expected output:
Original address: 0x7fff5fbff8a0Original values: id=42, name=test_data, value=3.14159Serialized: 140734799801504Recovered address: 0x7fff5fbff8a0✓ Pointer comparison: SAME ADDRESSRecovered values: id=42, name=test_data, value=3.14159Final Code
Section titled “Final Code”#ifndef DATA_HPP#define DATA_HPP
#include <string>
struct Data { int id; std::string name; double value;};
#endif#ifndef SERIALIZER_HPP#define SERIALIZER_HPP
#include <stdint.h>#include "Data.hpp"
class Serializer {private: Serializer(); Serializer(const Serializer& other); Serializer& operator=(const Serializer& other); ~Serializer();
public: static uintptr_t serialize(Data* ptr); static Data* deserialize(uintptr_t raw);};
#endif#include "Serializer.hpp"
Serializer::Serializer() {}Serializer::Serializer(const Serializer& other) { (void)other; }Serializer& Serializer::operator=(const Serializer& other) { (void)other; return *this; }Serializer::~Serializer() {}
uintptr_t Serializer::serialize(Data* ptr) { return reinterpret_cast<uintptr_t>(ptr);}
Data* Serializer::deserialize(uintptr_t raw) { return reinterpret_cast<Data*>(raw);}#include "Serializer.hpp"#include <iostream>
int main() { Data original; original.id = 42; original.name = "test_data"; original.value = 3.14159;
std::cout << "Original: " << &original << std::endl;
uintptr_t raw = Serializer::serialize(&original); std::cout << "Serialized: " << raw << std::endl;
Data* recovered = Serializer::deserialize(raw); std::cout << "Deserialized: " << recovered << std::endl;
if (recovered == &original) std::cout << "SUCCESS: Pointers match!" << std::endl; else std::cout << "FAILURE: Pointers differ!" << std::endl;
std::cout << "Data integrity: " << recovered->id << ", " << recovered->name << ", " << recovered->value << std::endl;
return 0;}Exercise 02: Identify Real Type
Section titled “Exercise 02: Identify Real Type”Subject Analysis
Section titled “Subject Analysis”Create a Base class with derived classes A, B, C. Implement:
Base* generate()- randomly returns new A, B, or Cvoid identify(Base* p)- prints actual type using pointervoid identify(Base& p)- prints actual type using reference
Key constraints:
<typeinfo>header is FORBIDDEN- Reference version cannot use pointers internally
- Base must have a virtual destructor
Approach Strategy
Section titled “Approach Strategy”Think before coding:
-
How to identify type without typeinfo?
dynamic_castreturns NULL on failure (pointers)dynamic_castthrowsstd::bad_caston failure (references)- Try each derived type until one succeeds
-
Why must Base be polymorphic?
dynamic_castonly works on polymorphic classes- A class is polymorphic if it has at least one virtual function
- Virtual destructor is the cleanest solution
-
Pointer vs Reference behavior:
// Pointer: returns NULL on failureA* a = dynamic_cast<A*>(base_ptr); // NULL if not A// Reference: throws on failureA& a = dynamic_cast<A&>(base_ref); // throws if not A
Progressive Code Building
Section titled “Progressive Code Building”Stage 1: Base and derived classes
#ifndef BASE_HPP#define BASE_HPP
class Base {public: virtual ~Base(); // Makes class polymorphic};
#endif#ifndef A_HPP#define A_HPP
#include "Base.hpp"
class A : public Base {public: ~A();};
#endif#ifndef B_HPP#define B_HPP
#include "Base.hpp"
class B : public Base {public: ~B();};
#endif#ifndef C_HPP#define C_HPP
#include "Base.hpp"
class C : public Base {public: ~C();};
#endifStage 2: Implementations
#include "Base.hpp"
Base::~Base() {}#include "A.hpp"
A::~A() {}(Similar for B.cpp and C.cpp)
Stage 3: Generator function
#include "A.hpp"#include "B.hpp"#include "C.hpp"#include <cstdlib>#include <ctime>
Base* generate() { // Seed random number generator (only once ideally) static bool seeded = false; if (!seeded) { std::srand(std::time(NULL)); seeded = true; }
switch (std::rand() % 3) { case 0: return new A(); case 1: return new B(); case 2: return new C(); } return NULL; // Should never reach}Stage 4: Pointer identification
#include <iostream>
void identify(Base* p) { if (dynamic_cast<A*>(p)) std::cout << "A" << std::endl; else if (dynamic_cast<B*>(p)) std::cout << "B" << std::endl; else if (dynamic_cast<C*>(p)) std::cout << "C" << std::endl; else std::cout << "Unknown" << std::endl;}Stage 5: Reference identification
void identify(Base& p) { try { (void)dynamic_cast<A&>(p); std::cout << "A" << std::endl; return; } catch (...) {}
try { (void)dynamic_cast<B&>(p); std::cout << "B" << std::endl; return; } catch (...) {}
try { (void)dynamic_cast<C&>(p); std::cout << "C" << std::endl; return; } catch (...) {}
std::cout << "Unknown" << std::endl;}Line-by-Line Explanation
Section titled “Line-by-Line Explanation”Pointer version logic:
| Code | What Happens |
|---|---|
dynamic_cast<A*>(p) | Attempts to cast p to A* |
| Returns non-NULL | p points to an A object (or derived from A) |
| Returns NULL | p does not point to an A object |
if (result) | Non-NULL pointer is truthy |
Reference version logic:
| Code | What Happens |
|---|---|
dynamic_cast<A&>(p) | Attempts to cast p to A& |
| Success | Returns valid reference to A |
| Failure | Throws std::bad_cast exception |
(void) | Discards result, we only care about success/failure |
return; | Exit function after successful identification |
Why (void) before dynamic_cast?
// Without (void): compiler warning about unused resultdynamic_cast<A&>(p); // Warning: expression result unused
// With (void): explicitly ignoring return value(void)dynamic_cast<A&>(p); // OK, intention is clearCommon Pitfalls
Section titled “Common Pitfalls”1. Using typeinfo (forbidden):
// WRONG: <typeinfo> is forbidden#include <typeinfo>if (typeid(*p) == typeid(A)) // NOT ALLOWED
// RIGHT: Use dynamic_castif (dynamic_cast<A*>(p))2. Using pointer in reference version:
// WRONG: Subject forbids using pointer in reference versionvoid identify(Base& p) { Base* ptr = &p; // NOT ALLOWED if (dynamic_cast<A*>(ptr)) ...}
// RIGHT: Use reference dynamic_cast with try/catchvoid identify(Base& p) { try { (void)dynamic_cast<A&>(p); std::cout << "A" << std::endl; return; } catch (...) {} // ...}3. Forgetting virtual destructor:
// WRONG: Base is not polymorphicclass Base {public: ~Base(); // Not virtual!};// dynamic_cast will fail at compile time
// RIGHT: Virtual destructor makes it polymorphicclass Base {public: virtual ~Base();};4. Catching wrong exception:
// WRONG: Specific exception type (requires <typeinfo>)catch (std::bad_cast& e) ...
// RIGHT: Catch all exceptionscatch (...) {}Testing Tips
Section titled “Testing Tips”#include "Base.hpp"#include "A.hpp"#include "B.hpp"#include "C.hpp"#include <iostream>
Base* generate();void identify(Base* p);void identify(Base& p);
int main() { // Test random generation multiple times for (int i = 0; i < 5; i++) { Base* obj = generate();
std::cout << "Test " << i + 1 << ": "; std::cout << "Pointer: "; identify(obj);
std::cout << " Reference: "; identify(*obj);
delete obj; std::cout << std::endl; }
// Test known types std::cout << "Known A: "; A a; identify(&a); identify(a);
std::cout << "Known B: "; B b; identify(&b); identify(b);
std::cout << "Known C: "; C c; identify(&c); identify(c);
return 0;}Test script:
# Run multiple times to see randomnessfor i in {1..5}; do ./identify echo "---"doneFinal Code
Section titled “Final Code”#ifndef BASE_HPP#define BASE_HPP
class Base {public: virtual ~Base();};
#endif#include "Base.hpp"
Base::~Base() {}#ifndef A_HPP#define A_HPP
#include "Base.hpp"
class A : public Base {public: ~A();};
#endif#include "A.hpp"
A::~A() {}#ifndef B_HPP#define B_HPP
#include "Base.hpp"
class B : public Base {public: ~B();};
#endif#include "B.hpp"
B::~B() {}#ifndef C_HPP#define C_HPP
#include "Base.hpp"
class C : public Base {public: ~C();};
#endif#include "C.hpp"
C::~C() {}#include "Base.hpp"#include "A.hpp"#include "B.hpp"#include "C.hpp"#include <cstdlib>#include <ctime>#include <iostream>
Base* generate() { static bool seeded = false; if (!seeded) { std::srand(std::time(NULL)); seeded = true; } switch (std::rand() % 3) { case 0: return new A(); case 1: return new B(); case 2: return new C(); } return NULL;}
void identify(Base* p) { if (dynamic_cast<A*>(p)) std::cout << "A" << std::endl; else if (dynamic_cast<B*>(p)) std::cout << "B" << std::endl; else if (dynamic_cast<C*>(p)) std::cout << "C" << std::endl; else std::cout << "Unknown" << std::endl;}
void identify(Base& p) { try { (void)dynamic_cast<A&>(p); std::cout << "A" << std::endl; return; } catch (...) {} try { (void)dynamic_cast<B&>(p); std::cout << "B" << std::endl; return; } catch (...) {} try { (void)dynamic_cast<C&>(p); std::cout << "C" << std::endl; return; } catch (...) {} std::cout << "Unknown" << std::endl;}#include "Base.hpp"#include "A.hpp"#include "B.hpp"#include "C.hpp"#include <iostream>
Base* generate();void identify(Base* p);void identify(Base& p);
int main() { for (int i = 0; i < 5; i++) { Base* obj = generate(); std::cout << "Pointer: "; identify(obj); std::cout << "Reference: "; identify(*obj); delete obj; std::cout << std::endl; } return 0;}Module 06 Summary: The Four C++ Casts
Section titled “Module 06 Summary: The Four C++ Casts”| Cast | Purpose | Exercise |
|---|---|---|
static_cast | Safe numeric conversions | Ex00: char/int/float/double |
reinterpret_cast | Bit-level reinterpretation | Ex01: pointer ↔ integer |
dynamic_cast | Runtime type identification | Ex02: polymorphic downcasting |
const_cast | Remove/add const | Not covered (rarely needed) |
When to use each:
| Need | Use |
|---|---|
| Convert between numeric types | static_cast<target>(value) |
| Upcast (derived → base) | Implicit or static_cast |
| Downcast (base → derived) | dynamic_cast (safest) or static_cast (if certain) |
| Pointer ↔ integer | reinterpret_cast |
| Remove const | const_cast (avoid if possible) |