cpp

exercises

exercises.cpp⚙️
/**
 * Rule of 3/5/0, Move Semantics & RAII - Exercises
 * Practice problems for resource management in C++
 */

#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <cassert>

using std::cout;
using std::endl;

// ============================================================================
// EXERCISE 1: Implement Rule of Three
// ============================================================================

/**
 * TODO: Fix this IntArray class to properly implement the Rule of Three.
 * Currently, it has a memory management bug.
 * 
 * Requirements:
 * 1. Implement copy constructor (deep copy)
 * 2. Implement copy assignment operator (deep copy)
 * 3. Destructor is already provided
 * 
 * Test: After fixing, the test should run without memory issues
 */

class IntArray {
    int* data;
    size_t size;
    
public:
    IntArray(size_t n) : size(n) {
        data = new int[size];
        for (size_t i = 0; i < size; ++i) {
            data[i] = 0;
        }
    }
    
    ~IntArray() {
        delete[] data;
    }
    
    // TODO: Add copy constructor
    // IntArray(const IntArray& other) { ... }
    
    // TODO: Add copy assignment operator
    // IntArray& operator=(const IntArray& other) { ... }
    
    int& operator[](size_t index) { return data[index]; }
    int operator[](size_t index) const { return data[index]; }
    size_t getSize() const { return size; }
};

void testExercise1() {
    cout << "=== EXERCISE 1: Rule of Three ===" << endl;
    
    /*
    IntArray arr1(5);
    for (size_t i = 0; i < 5; ++i) arr1[i] = i * 10;
    
    // Test copy constructor
    IntArray arr2 = arr1;
    arr2[0] = 999;
    
    // Test copy assignment
    IntArray arr3(3);
    arr3 = arr1;
    arr3[1] = 888;
    
    // Verify independence (deep copy)
    assert(arr1[0] == 0);   // Should NOT be 999
    assert(arr1[1] == 10);  // Should NOT be 888
    
    cout << "arr1[0] = " << arr1[0] << " (expected 0)" << endl;
    cout << "arr2[0] = " << arr2[0] << " (expected 999)" << endl;
    cout << "arr3[1] = " << arr3[1] << " (expected 888)" << endl;
    
    cout << "Exercise 1 PASSED!" << endl;
    */
    
    cout << "Uncomment test code after implementing Rule of Three" << endl;
}

// ============================================================================
// EXERCISE 2: Implement Rule of Five
// ============================================================================

/**
 * TODO: Extend IntArray to implement the Rule of Five.
 * Add move semantics to the class.
 * 
 * Requirements:
 * 1. Keep copy constructor and copy assignment
 * 2. Add move constructor
 * 3. Add move assignment operator
 * 4. Both move operations should be noexcept
 * 5. Moved-from object should be in a valid but empty state
 */

class IntArray5 {
    int* data;
    size_t size;
    
public:
    IntArray5(size_t n) : size(n), data(n > 0 ? new int[n]() : nullptr) {}
    
    ~IntArray5() {
        delete[] data;
    }
    
    // Copy constructor
    IntArray5(const IntArray5& other) : size(other.size) {
        data = new int[size];
        for (size_t i = 0; i < size; ++i) {
            data[i] = other.data[i];
        }
    }
    
    // Copy assignment (copy-and-swap)
    IntArray5& operator=(IntArray5 other) {
        swap(*this, other);
        return *this;
    }
    
    // TODO: Add move constructor
    // IntArray5(IntArray5&& other) noexcept { ... }
    
    // TODO: Add move assignment operator
    // IntArray5& operator=(IntArray5&& other) noexcept { ... }
    
    friend void swap(IntArray5& a, IntArray5& b) noexcept {
        using std::swap;
        swap(a.data, b.data);
        swap(a.size, b.size);
    }
    
    int& operator[](size_t index) { return data[index]; }
    size_t getSize() const { return size; }
    bool isEmpty() const { return size == 0 || data == nullptr; }
};

void testExercise2() {
    cout << "\n=== EXERCISE 2: Rule of Five ===" << endl;
    
    /*
    IntArray5 arr1(1000000);  // Large array
    arr1[0] = 42;
    
    // Move constructor
    IntArray5 arr2 = std::move(arr1);
    assert(arr1.isEmpty());    // arr1 should be empty after move
    assert(arr2[0] == 42);
    
    // Move assignment
    IntArray5 arr3(100);
    arr3 = std::move(arr2);
    assert(arr2.isEmpty());    // arr2 should be empty after move
    assert(arr3[0] == 42);
    
    cout << "Move constructor: PASSED" << endl;
    cout << "Move assignment: PASSED" << endl;
    cout << "Exercise 2 PASSED!" << endl;
    */
    
    cout << "Uncomment test code after implementing move operations" << endl;
}

// ============================================================================
// EXERCISE 3: RAII File Wrapper
// ============================================================================

/**
 * TODO: Create an RAII wrapper for file operations.
 * 
 * Requirements:
 * 1. Constructor opens file
 * 2. Destructor closes file
 * 3. Non-copyable (delete copy operations)
 * 4. Movable (implement move operations)
 * 5. Provide writeLine() method
 * 6. Throw exception if file cannot be opened
 */

class SafeFile {
    // TODO: Add file handle member
    // FILE* file;
    
public:
    // TODO: Constructor - open file
    // SafeFile(const std::string& path, const char* mode) { ... }
    
    // TODO: Destructor - close file
    // ~SafeFile() { ... }
    
    // TODO: Delete copy operations
    
    // TODO: Implement move operations
    
    // TODO: writeLine method
    // void writeLine(const std::string& line) { ... }
    
    // TODO: isOpen check
    // bool isOpen() const { ... }
};

void testExercise3() {
    cout << "\n=== EXERCISE 3: RAII File Wrapper ===" << endl;
    
    /*
    try {
        {
            SafeFile file("/tmp/exercise3.txt", "w");
            file.writeLine("Hello RAII!");
            file.writeLine("This file is safely managed.");
        }  // File automatically closed here
        
        cout << "File written and closed successfully!" << endl;
        
        // Move test
        SafeFile f1("/tmp/exercise3_move.txt", "w");
        SafeFile f2 = std::move(f1);
        assert(!f1.isOpen());  // f1 should be empty
        assert(f2.isOpen());   // f2 should have the file
        f2.writeLine("Moved file handle");
        
        cout << "Move semantics: PASSED" << endl;
        cout << "Exercise 3 PASSED!" << endl;
        
    } catch (const std::exception& e) {
        cout << "Error: " << e.what() << endl;
    }
    */
    
    cout << "Uncomment test code after implementing SafeFile" << endl;
}

// ============================================================================
// EXERCISE 4: RAII Resource Manager with Callback
// ============================================================================

/**
 * TODO: Create a ScopeGuard class that runs a cleanup function on destruction.
 * 
 * Usage:
 *   ScopeGuard guard([]() { cout << "Cleanup!" << endl; });
 *   // ... do work ...
 *   // When guard goes out of scope, cleanup runs automatically
 * 
 * Requirements:
 * 1. Store cleanup function (use std::function<void()>)
 * 2. Execute function in destructor
 * 3. Non-copyable
 * 4. Provide dismiss() method to prevent cleanup
 */

#include <functional>

class ScopeGuard {
    // TODO: Add cleanup function member
    // std::function<void()> cleanup;
    // bool dismissed;
    
public:
    // TODO: Constructor
    // explicit ScopeGuard(std::function<void()> fn) { ... }
    
    // TODO: Destructor
    // ~ScopeGuard() { ... }
    
    // TODO: Delete copy operations
    
    // TODO: dismiss() method
    // void dismiss() { ... }
};

void testExercise4() {
    cout << "\n=== EXERCISE 4: Scope Guard ===" << endl;
    
    /*
    int value = 0;
    
    // Test automatic cleanup
    {
        ScopeGuard guard([&value]() { value = 100; });
        value = 50;
    }
    assert(value == 100);
    cout << "Automatic cleanup: PASSED" << endl;
    
    // Test dismiss
    value = 0;
    {
        ScopeGuard guard([&value]() { value = 999; });
        value = 50;
        guard.dismiss();
    }
    assert(value == 50);  // Cleanup should NOT have run
    cout << "Dismiss: PASSED" << endl;
    
    cout << "Exercise 4 PASSED!" << endl;
    */
    
    cout << "Uncomment test code after implementing ScopeGuard" << endl;
}

// ============================================================================
// EXERCISE 5: Rule of Zero
// ============================================================================

/**
 * TODO: Refactor this ResourceOwner class to use Rule of Zero.
 * Replace raw pointers with smart pointers and standard containers.
 * After refactoring, you should be able to delete all special members.
 * 
 * Current class has memory leaks and copy issues!
 */

// BROKEN VERSION - DO NOT USE
class BrokenResourceOwner {
    int* data;
    char* name;
    int* history;
    size_t historySize;
    
public:
    BrokenResourceOwner(int val, const char* n) {
        data = new int(val);
        name = new char[strlen(n) + 1];
        strcpy(name, n);
        history = new int[100];
        historySize = 0;
    }
    
    // MEMORY LEAK: No destructor!
    // COPY BUG: No copy operations!
    
    void addToHistory(int val) {
        if (historySize < 100) {
            history[historySize++] = val;
        }
    }
};

// TODO: Implement this using Rule of Zero
class ResourceOwner {
    // TODO: Use smart pointers and containers
    // std::unique_ptr<int> data;
    // std::string name;
    // std::vector<int> history;
    
public:
    // TODO: Implement constructor
    // ResourceOwner(int val, const std::string& n) { ... }
    
    // NO destructor, copy, or move operations needed!
    // The compiler-generated defaults will work correctly.
    
    // TODO: addToHistory method
    // void addToHistory(int val) { ... }
    
    // TODO: print method
    // void print() const { ... }
};

void testExercise5() {
    cout << "\n=== EXERCISE 5: Rule of Zero ===" << endl;
    
    /*
    ResourceOwner r1(42, "Resource1");
    r1.addToHistory(1);
    r1.addToHistory(2);
    
    // Copy should work automatically (deep copy)
    ResourceOwner r2 = r1;
    r2.addToHistory(3);
    
    // Move should work automatically
    ResourceOwner r3 = std::move(r1);
    
    r2.print();
    r3.print();
    
    cout << "Rule of Zero works!" << endl;
    cout << "Exercise 5 PASSED!" << endl;
    */
    
    cout << "Uncomment test code after implementing ResourceOwner" << endl;
}

// ============================================================================
// EXERCISE 6: Smart Pointer Practice
// ============================================================================

/**
 * TODO: Complete these smart pointer exercises.
 */

class Widget {
    int id;
    static int counter;
public:
    Widget() : id(++counter) {
        cout << "  Widget " << id << " created" << endl;
    }
    ~Widget() {
        cout << "  Widget " << id << " destroyed" << endl;
    }
    int getId() const { return id; }
};

int Widget::counter = 0;

void testExercise6() {
    cout << "\n=== EXERCISE 6: Smart Pointer Practice ===" << endl;
    
    cout << "\n6a) unique_ptr basics:" << endl;
    // TODO: Create a unique_ptr<Widget> using make_unique
    // auto w1 = ???
    
    // TODO: Transfer ownership to w2
    // auto w2 = std::move(???);
    
    // Verify w1 is null and w2 owns the widget
    
    cout << "\n6b) shared_ptr reference counting:" << endl;
    // TODO: Create shared_ptr and verify reference count
    // auto s1 = std::make_shared<Widget>();
    // auto s2 = s1;
    // cout << "Reference count: " << s1.use_count() << endl;
    
    cout << "\n6c) unique_ptr with array:" << endl;
    // TODO: Create unique_ptr for array of 5 ints
    // auto arr = std::make_unique<int[]>(5);
    // arr[0] = 10;
    
    cout << "\n6d) Custom deleter:" << endl;
    // TODO: Create unique_ptr with custom deleter that prints message
    
    cout << "Exercise 6: Complete the TODOs!" << endl;
}

// ============================================================================
// EXERCISE 7: Exception Safety with RAII
// ============================================================================

/**
 * TODO: Make this function exception-safe using RAII.
 * Currently, resources may leak if an exception is thrown.
 */

void brokenProcess() {
    // PROBLEM: If processData throws, cleanup never happens!
    
    // Allocate resources
    int* data = new int[1000];
    FILE* file = fopen("/tmp/data.txt", "w");
    
    // Some processing that might throw
    // processData(data, file);  // Might throw!
    
    // Cleanup (might never be reached!)
    fclose(file);
    delete[] data;
}

void testExercise7() {
    cout << "\n=== EXERCISE 7: Exception Safety ===" << endl;
    
    // TODO: Rewrite brokenProcess using RAII
    /*
    void safeProcess() {
        // Use smart pointers and RAII wrappers
        auto data = std::make_unique<int[]>(1000);
        
        // Use SafeFile from Exercise 3 or similar
        // SafeFile file("/tmp/data.txt", "w");
        
        // Now this is safe - resources cleaned up even on exception
        // processData(data.get(), file.get());
    }
    */
    
    cout << "Implement safeProcess using RAII!" << endl;
}

// ============================================================================
// EXERCISE 8: Move Semantics Optimization
// ============================================================================

/**
 * TODO: Optimize this code using move semantics.
 * Count the number of copies and moves.
 */

class HeavyObject {
    std::vector<int> data;
    static int copies;
    static int moves;
    
public:
    HeavyObject() : data(10000) {}
    
    HeavyObject(const HeavyObject& other) : data(other.data) {
        ++copies;
        cout << "  COPY #" << copies << endl;
    }
    
    HeavyObject(HeavyObject&& other) noexcept : data(std::move(other.data)) {
        ++moves;
        cout << "  MOVE #" << moves << endl;
    }
    
    HeavyObject& operator=(const HeavyObject& other) {
        data = other.data;
        ++copies;
        return *this;
    }
    
    HeavyObject& operator=(HeavyObject&& other) noexcept {
        data = std::move(other.data);
        ++moves;
        return *this;
    }
    
    static void resetStats() { copies = moves = 0; }
    static void printStats() {
        cout << "Copies: " << copies << ", Moves: " << moves << endl;
    }
};

int HeavyObject::copies = 0;
int HeavyObject::moves = 0;

void testExercise8() {
    cout << "\n=== EXERCISE 8: Move Semantics Optimization ===" << endl;
    
    cout << "\nUnoptimized version:" << endl;
    HeavyObject::resetStats();
    {
        std::vector<HeavyObject> vec;
        HeavyObject obj;
        vec.push_back(obj);  // TODO: How to optimize?
        vec.push_back(obj);
    }
    HeavyObject::printStats();
    
    // TODO: Optimize using std::move() and emplace_back()
    /*
    cout << "\nOptimized version:" << endl;
    HeavyObject::resetStats();
    {
        std::vector<HeavyObject> vec;
        vec.reserve(2);  // Prevent reallocation
        vec.emplace_back();  // Construct in place
        vec.emplace_back();
    }
    HeavyObject::printStats();
    */
    
    cout << "Goal: Reduce copies to 0!" << endl;
}

// ============================================================================
// MAIN
// ============================================================================

int main() {
    cout << "╔══════════════════════════════════════════════════════════════╗" << endl;
    cout << "║     RULE OF 3/5/0, MOVE SEMANTICS & RAII EXERCISES           ║" << endl;
    cout << "╚══════════════════════════════════════════════════════════════╝" << endl;
    
    testExercise1();
    testExercise2();
    testExercise3();
    testExercise4();
    testExercise5();
    testExercise6();
    testExercise7();
    testExercise8();
    
    cout << "\n═══════════════════════════════════════════════════════════════" << endl;
    cout << "Complete all exercises by implementing the TODO sections!" << endl;
    cout << "Remember:" << endl;
    cout << "  - Rule of 3: Destructor, Copy Constructor, Copy Assignment" << endl;
    cout << "  - Rule of 5: Add Move Constructor, Move Assignment" << endl;
    cout << "  - Rule of 0: Use smart pointers and containers" << endl;
    cout << "  - RAII: Acquire in constructor, release in destructor" << endl;
    
    return 0;
}
Exercises - C++ Tutorial | DeepML