Skip to content

melaniereis/CPP_Module06

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

9 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿ”„ CPP Module 06 - Casts

C++ 42 School Module Status

Mastering Type Conversions and Cast Operators

๐Ÿ“– Overview

CPP Module 06 explores the intricate world of type conversions and cast operators in C++. Through scalar conversions, serialization, and runtime type identification, you'll master the four fundamental C++ casts and understand when and how to safely convert between different data types while maintaining type safety.

๐ŸŽฏ Learning Objectives

  • Master the four C++ cast operators and their appropriate usage
  • Implement safe scalar type conversions with validation
  • Understand pointer serialization and deserialization
  • Learn runtime type identification with dynamic_cast
  • Practice type safety and conversion error handling
  • Explore reinterpret_cast for low-level memory operations
  • Implement robust type checking and validation systems

๐Ÿ’ก Key Concepts Introduced

  • Static Cast: Safe conversions between related types
  • Dynamic Cast: Runtime type checking for polymorphic types
  • Const Cast: Adding or removing const qualifiers
  • Reinterpret Cast: Low-level bit pattern reinterpretation
  • Type Safety: Preventing dangerous type conversions
  • Serialization: Converting objects to/from raw data
  • RTTI: Runtime Type Information and identification

๐ŸŽญ The Four Cast Operators

static_cast<T>()

  • Purpose: Safe, checked conversions between related types
  • Usage: Numeric conversions, base-to-derived (when safe)
  • Check: Compile-time validation

dynamic_cast<T>()

  • Purpose: Safe downcasting with runtime type checking
  • Usage: Polymorphic type conversions
  • Check: Runtime validation, returns nullptr on failure

const_cast<T>()

  • Purpose: Add or remove const/volatile qualifiers
  • Usage: Interfacing with legacy code
  • Warning: Use sparingly, can break const correctness

reinterpret_cast<T>()

  • Purpose: Low-level bit pattern reinterpretation
  • Usage: Pointer-to-integer conversions, platform-specific code
  • Warning: Dangerous, bypasses type system

๐Ÿš€ Exercises

๐Ÿ”ข Exercise 00: Conversion of Scalar Types

Files: ScalarConverter.cpp, ScalarConverter.hpp, main.cpp

Comprehensive scalar type conversion system with validation, demonstrating:

  • Static cast for safe numeric conversions
  • Input validation and type detection
  • Conversion between char, int, float, and double
  • Special value handling (inf, nan, pseudo-literals)
  • Error handling and impossible conversion detection

ScalarConverter Class:

class ScalarConverter {
public:
    static void convert(const std::string &input);

private:
    ScalarConverter();                        // Static class - no instances
    ~ScalarConverter();
    ScalarConverter(const ScalarConverter &other);
    ScalarConverter &operator=(const ScalarConverter &other);

    // Type detection functions
    static bool isChar(const std::string &str);
    static bool isInt(const std::string &str);
    static bool isFloat(const std::string &str);
    static bool isDouble(const std::string &str);
    static bool isPseudoLiteral(const std::string &str);

    // Conversion functions
    static void convertFromChar(const std::string &str);
    static void convertFromInt(const std::string &str);
    static void convertFromFloat(const std::string &str);
    static void convertFromDouble(const std::string &str);
    static void convertPseudoLiteral(const std::string &str);

    // Output functions
    static void printChar(double value);
    static void printInt(double value);
    static void printFloat(double value);
    static void printDouble(double value);
};

Type Detection Logic:

bool ScalarConverter::isChar(const std::string &str) {
    return (str.length() == 1 && isprint(str[0]) && !isdigit(str[0]));
}

bool ScalarConverter::isInt(const std::string &str) {
    if (str.empty()) return false;

    size_t start = (str[0] == '+' || str[0] == '-') ? 1 : 0;
    if (start >= str.length()) return false;

    for (size_t i = start; i < str.length(); i++) {
        if (!isdigit(str[i])) return false;
    }
    return true;
}

bool ScalarConverter::isFloat(const std::string &str) {
    if (str.length() < 2 || str[str.length() - 1] != 'f') return false;

    std::string without_f = str.substr(0, str.length() - 1);
    return isValidNumber(without_f);
}

Conversion Implementation:

void ScalarConverter::convert(const std::string &input) {
    if (input.empty()) {
        std::cout << ERROR_EMPTY << std::endl;
        return;
    }

    // Type detection with function pointer array
    CheckFunc checkers[] = {
        &ScalarConverter::isChar,
        &ScalarConverter::isInt,
        &ScalarConverter::isFloat,
        &ScalarConverter::isDouble,
        &ScalarConverter::isPseudoLiteral
    };

    ConvertFunc converters[] = {
        &ScalarConverter::convertFromChar,
        &ScalarConverter::convertFromInt,
        &ScalarConverter::convertFromFloat,
        &ScalarConverter::convertFromDouble,
        &ScalarConverter::convertPseudoLiteral
    };

    for (int i = 0; i < 5; i++) {
        if (checkers[i](input)) {
            converters[i](input);
            return;
        }
    }

    std::cout << ERROR_INVALID_FORMAT << std::endl;
}

Safe Static Cast Usage:

void ScalarConverter::convertFromInt(const std::string &str) {
    long long value = std::strtoll(str.c_str(), NULL, 10);

    // Check for overflow
    if (value > std::numeric_limits<int>::max() ||
        value < std::numeric_limits<int>::min()) {
        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;
    }

    int intValue = static_cast<int>(value);
    double doubleValue = static_cast<double>(intValue);

    printChar(doubleValue);
    std::cout << "int: " << intValue << std::endl;
    std::cout << "float: " << static_cast<float>(doubleValue) << "f" << std::endl;
    std::cout << "double: " << doubleValue << std::endl;
}

Special Value Handling:

void ScalarConverter::convertPseudoLiteral(const std::string &str) {
    std::cout << "char: impossible" << std::endl;
    std::cout << "int: impossible" << std::endl;

    if (str == "inff" || str == "+inff") {
        std::cout << "float: +inff" << std::endl;
        std::cout << "double: +inf" << std::endl;
    } else if (str == "-inff") {
        std::cout << "float: -inff" << std::endl;
        std::cout << "double: -inf" << std::endl;
    } else if (str == "nanf") {
        std::cout << "float: nanf" << std::endl;
        std::cout << "double: nan" << std::endl;
    }
    // Handle double versions...
}

Usage Examples:

./scalar_converter "42"
# Output:
# char: '*'
# int: 42
# float: 42.0f
# double: 42.0

./scalar_converter "42.0f"
# Output:
# char: '*'
# int: 42
# float: 42.0f
# double: 42.0

./scalar_converter "inff"
# Output:
# char: impossible
# int: impossible
# float: +inff
# double: +inf

Key Learning Points:

  • Static cast for safe numeric conversions
  • Type detection and validation algorithms
  • Special floating-point value handling
  • Error reporting and impossible conversion detection
  • Function pointer arrays for dispatch

๐Ÿ” Exercise 01: Serialization

Files: Serializer.cpp, Serializer.hpp, Data.cpp, Data.hpp, main.cpp

Pointer serialization and deserialization using reinterpret_cast, showcasing:

  • Reinterpret cast for pointer-to-integer conversion
  • Data integrity preservation across serialization
  • Static utility class design
  • Serialization round-trip testing

Data Structure:

class Data {
public:
    Data();
    Data(const std::string &name, const std::string &profession, int id);
    Data(const Data &other);
    Data &operator=(const Data &other);
    ~Data();

    // Getters
    const std::string &getName() const;
    const std::string &getProfession() const;
    int getId() const;

    // Setters
    void setName(const std::string &name);
    void setProfession(const std::string &profession);
    void setId(int id);

    // Utility
    void display() const;
    bool operator==(const Data &other) const;

private:
    std::string _name;
    std::string _profession;
    int _id;
};

Serializer Class (Static Utility):

class Serializer {
public:
    static uintptr_t serialize(Data *ptr);
    static Data *deserialize(uintptr_t raw);

private:
    Serializer();                             // Static class - no instances
    ~Serializer();
    Serializer(const Serializer &other);
    Serializer &operator=(const Serializer &other);
};

Serialization Implementation:

uintptr_t Serializer::serialize(Data *ptr) {
    return reinterpret_cast<uintptr_t>(ptr);
}

Data *Serializer::deserialize(uintptr_t raw) {
    return reinterpret_cast<Data *>(raw);
}

Comprehensive Testing:

void testSerialization() {
    // Create original data
    Data *original = new Data("Alice Johnson", "Software Engineer", 12345);
    std::cout << "Original Data:" << std::endl;
    original->display();

    // Serialize to integer
    uintptr_t serialized = Serializer::serialize(original);
    std::cout << "\nSerialized address: 0x" << std::hex << serialized << std::dec << std::endl;

    // Deserialize back to pointer
    Data *deserialized = Serializer::deserialize(serialized);
    std::cout << "\nDeserialized Data:" << std::endl;
    deserialized->display();

    // Verify integrity
    std::cout << "\nIntegrity Check:" << std::endl;
    std::cout << "Pointers equal: " << (original == deserialized) << std::endl;
    std::cout << "Data equal: " << (*original == *deserialized) << std::endl;
    std::cout << "Same memory address: " << (original == deserialized) << std::endl;

    // Cleanup
    delete original;  // Note: original and deserialized point to same memory
}

Advanced Serialization Tests:

void testMultipleSerialization() {
    std::vector<Data*> dataVector;
    std::vector<uintptr_t> serializedVector;

    // Create multiple data objects
    dataVector.push_back(new Data("Bob Smith", "Designer", 67890));
    dataVector.push_back(new Data("Carol Wilson", "Manager", 11111));
    dataVector.push_back(new Data("Dave Brown", "Tester", 22222));

    // Serialize all
    for (size_t i = 0; i < dataVector.size(); i++) {
        serializedVector.push_back(Serializer::serialize(dataVector[i]));
    }

    // Deserialize and verify
    for (size_t i = 0; i < serializedVector.size(); i++) {
        Data *restored = Serializer::deserialize(serializedVector[i]);
        std::cout << "Object " << i << " integrity: "
                  << (dataVector[i] == restored) << std::endl;
    }

    // Cleanup
    for (size_t i = 0; i < dataVector.size(); i++) {
        delete dataVector[i];
    }
}

Key Learning Points:

  • Reinterpret cast for pointer-to-integer conversions
  • Data integrity preservation across serialization
  • Static utility class design patterns
  • Memory address preservation and validation
  • Round-trip serialization testing

๐ŸŽฏ Exercise 02: Identify Real Type

Files: Base.cpp, Base.hpp, main.cpp

Runtime type identification using dynamic_cast, demonstrating:

  • Dynamic cast for safe downcasting
  • Runtime type information (RTTI)
  • Polymorphic type identification
  • Exception handling in type identification

Polymorphic Class Hierarchy:

class Base {
public:
    virtual ~Base();                          // Virtual destructor for RTTI
};

class A : public Base {};
class B : public Base {};
class C : public Base {};

Factory Function:

Base *generate(void) {
    static bool seeded = false;
    if (!seeded) {
        std::srand(std::time(NULL));
        seeded = true;
    }

    // Factory function pointer array
    Base* (*creators[])() = {
        []() -> Base* { return new A; },
        []() -> Base* { return new B; },
        []() -> Base* { return new C; }
    };

    int random = std::rand() % 3;
    std::cout << "Generated type: " << (char)('A' + random) << std::endl;
    return creators[random]();
}

Type Identification Functions:

By Pointer (Safe):

void identify(Base* p) {
    if (p == NULL) {
        std::cout << "Error: Null pointer provided" << std::endl;
        return;
    }

    std::cout << "Identifying by pointer: ";

    // Try dynamic_cast to each derived type
    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 type" << std::endl;
    }
}

By Reference (Exception-Based):

void identify(Base& p) {
    std::cout << "Identifying by reference: ";

    // Try dynamic_cast with references - throws std::bad_cast on failure
    try {
        (void)dynamic_cast<A&>(p);
        std::cout << "A" << std::endl;
        return;
    } catch (const std::bad_cast&) {}

    try {
        (void)dynamic_cast<B&>(p);
        std::cout << "B" << std::endl;
        return;
    } catch (const std::bad_cast&) {}

    try {
        (void)dynamic_cast<C&>(p);
        std::cout << "C" << std::endl;
        return;
    } catch (const std::bad_cast&) {}

    std::cout << "Unknown type" << std::endl;
}

Comprehensive Testing:

void testTypeIdentification() {
    std::cout << "=== Type Identification Tests ===" << std::endl;

    // Test multiple random generations
    for (int i = 0; i < 10; i++) {
        std::cout << "\nTest " << (i + 1) << ":" << std::endl;

        Base *obj = generate();

        // Test both identification methods
        identify(obj);      // By pointer
        identify(*obj);     // By reference

        delete obj;
    }
}

Advanced Testing with Known Types:

void testKnownTypes() {
    std::cout << "\n=== Known Type Tests ===" << std::endl;

    Base *objects[] = {
        new A(),
        new B(),
        new C()
    };

    const char types[] = {'A', 'B', 'C'};

    for (int i = 0; i < 3; i++) {
        std::cout << "\nTesting known type " << types[i] << ":" << std::endl;
        identify(objects[i]);
        identify(*objects[i]);
        delete objects[i];
    }
}

Edge Case Testing:

void testEdgeCases() {
    std::cout << "\n=== Edge Case Tests ===" << std::endl;

    // Test null pointer
    std::cout << "Testing null pointer:" << std::endl;
    Base *nullPtr = NULL;
    identify(nullPtr);

    // Test base class instance (if allowed)
    std::cout << "\nTesting base class:" << std::endl;
    Base *base = new Base();  // If concrete base allowed
    identify(base);
    identify(*base);
    delete base;
}

Key Learning Points:

  • Dynamic cast for runtime type checking
  • Difference between pointer and reference dynamic_cast
  • Exception handling with std::bad_cast
  • Runtime Type Information (RTTI) usage
  • Safe downcasting techniques

๐Ÿ› ๏ธ Compilation

Each exercise includes a Makefile with standard targets:

# Compile the program
make

# Clean object files
make clean

# Clean everything
make fclean

# Recompile
make re

Compilation flags:

c++ -Wall -Wextra -Werror -std=c++98

๐ŸŽฎ How to Run

Exercise 00 - Conversion of Scalar Types

cd ex00
make
./scalar_converter "42"
./scalar_converter "42.0f"
./scalar_converter "'a'"
./scalar_converter "inff"

Exercise 01 - Serialization

cd ex01
make
./serializer

Exercise 02 - Identify Real Type

cd ex02
make
./identify

๐Ÿงช Testing Examples

Scalar Conversion Test

# Test various input types
./scalar_converter "0"          # char: Non displayable, int: 0, float: 0.0f, double: 0.0
./scalar_converter "127"        # char: '\x7f', int: 127, float: 127.0f, double: 127.0
./scalar_converter "128"        # char: impossible, int: 128, float: 128.0f, double: 128.0
./scalar_converter "42.0f"      # char: '*', int: 42, float: 42.0f, double: 42.0
./scalar_converter "'z'"        # char: 'z', int: 122, float: 122.0f, double: 122.0
./scalar_converter "inff"       # char: impossible, int: impossible, float: +inff, double: +inf

Serialization Integrity Test

Data original("Test User", "Developer", 123);
uintptr_t serialized = Serializer::serialize(&original);
Data *restored = Serializer::deserialize(serialized);
assert(&original == restored);  // Same memory address

Dynamic Cast Test

Base *obj = generate();
A *a_ptr = dynamic_cast<A*>(obj);
if (a_ptr) {
    std::cout << "Successfully cast to A" << std::endl;
} else {
    std::cout << "Not an A object" << std::endl;
}

๐Ÿ—๏ธ Project Structure

CPP_Module06/
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ ex00/                    # Conversion of Scalar Types
โ”‚   โ”œโ”€โ”€ Makefile
โ”‚   โ”œโ”€โ”€ inc/
โ”‚   โ”‚   โ”œโ”€โ”€ ansi.h
โ”‚   โ”‚   โ””โ”€โ”€ ScalarConverter.hpp
โ”‚   โ””โ”€โ”€ src/
โ”‚       โ”œโ”€โ”€ ScalarConverter.cpp
โ”‚       โ””โ”€โ”€ main.cpp
โ”œโ”€โ”€ ex01/                    # Serialization
โ”‚   โ”œโ”€โ”€ Makefile
โ”‚   โ”œโ”€โ”€ inc/
โ”‚   โ”‚   โ”œโ”€โ”€ ansi.h
โ”‚   โ”‚   โ”œโ”€โ”€ Serializer.hpp
โ”‚   โ”‚   โ””โ”€โ”€ Data.hpp
โ”‚   โ””โ”€โ”€ src/
โ”‚       โ”œโ”€โ”€ Serializer.cpp
โ”‚       โ”œโ”€โ”€ Data.cpp
โ”‚       โ””โ”€โ”€ main.cpp
โ””โ”€โ”€ ex02/                    # Identify Real Type
    โ”œโ”€โ”€ Makefile
    โ”œโ”€โ”€ inc/
    โ”‚   โ”œโ”€โ”€ ansi.h
    โ”‚   โ””โ”€โ”€ Base.hpp
    โ””โ”€โ”€ src/
        โ”œโ”€โ”€ Base.cpp
        โ””โ”€โ”€ main.cpp

๐Ÿ’ก Key Takeaways

  1. Cast Safety: Understanding when each cast operator is appropriate
  2. Type Safety: Maintaining type safety while performing conversions
  3. Runtime Checking: Using dynamic_cast for safe downcasting
  4. Serialization: Converting objects to/from raw data safely
  5. RTTI: Leveraging runtime type information effectively
  6. Error Handling: Proper validation and error reporting in conversions
  7. Static Design: Creating utility classes with static-only interfaces

๐ŸŽฏ Skills Developed

  • โœ… Four C++ cast operators mastery
  • โœ… Safe scalar type conversions
  • โœ… Pointer serialization techniques
  • โœ… Runtime type identification
  • โœ… Dynamic cast exception handling
  • โœ… Input validation and error handling
  • โœ… Static utility class design
  • โœ… Type safety preservation techniques

โš ๏ธ Cast Usage Guidelines

static_cast<T>()

  • โœ… Use for: Numeric conversions, explicit type conversions
  • โœ… Safe when: Types are related, conversion is well-defined
  • โŒ Avoid for: Unrelated pointer types, const removal

dynamic_cast<T>()

  • โœ… Use for: Safe downcasting, type identification
  • โœ… Requires: Virtual functions (RTTI enabled)
  • โŒ Avoid for: Non-polymorphic types, performance-critical code

const_cast<T>()

  • โœ… Use for: Interfacing with legacy APIs
  • โš ๏ธ Caution: Can break const correctness
  • โŒ Never: Modify originally const objects

reinterpret_cast<T>()

  • โš ๏ธ Use sparingly: Platform-specific code, serialization
  • โš ๏ธ Dangerous: Bypasses type system completely
  • โŒ Avoid: Unless absolutely necessary and well-documented

๐Ÿ”— Type Safety Best Practices

  1. Prefer static_cast: Use for most conversions
  2. Validate inputs: Check ranges and validity
  3. Handle failures: dynamic_cast can return nullptr
  4. Document intent: Explain why specific casts are used
  5. Test thoroughly: Verify conversion accuracy
  6. Avoid C-style casts: Use explicit C++ casts
  7. Consider alternatives: Maybe redesign instead of casting

๐Ÿ”— Common Pitfalls & Solutions

  1. Implicit Conversions: Be explicit about type conversions
  2. Precision Loss: Check numeric conversion ranges
  3. Null Pointers: Always validate dynamic_cast results
  4. Const Violations: Avoid const_cast unless absolutely necessary
  5. Platform Dependencies: Document reinterpret_cast usage
  6. Performance: dynamic_cast has runtime overhead
  7. Exception Safety: Handle bad_cast exceptions properly

๐Ÿ”— Next Steps

After mastering Module 06, you'll be ready to tackle:

  • Module 07: Templates and generic programming
  • Module 08: STL containers and iterators
  • Module 09: Advanced STL algorithms and performance

"Type safety is not a restriction, it's a foundation for reliable software."

Module 06 Complete โœจ | Previous: โ† Module 05 | Next: Module 07 โ†’

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published