Exception Handling and File I/O

Exception Handling and File I/O

1. What is Exception Handling?

Exceptions are abnormal situations that occur during program execution. C++ handles exceptions using try-catch syntax.

┌─────────────────────────────────────────────┐
│           Exception Handling Flow            │
└─────────────────────────────────────────────┘
                         ┌──────────────┴──────────────┐
                                   ▼
┌─────────┐                   ┌─────────┐
│   try    Exception─────▶    throw  │
│  block                              │
└─────────┘                   └─────────┘
                                         No exception                   Exception propagation
                                   ▼
┌─────────┐                   ┌─────────┐
│ Normal                       catch  │
│  exit                        block  │
└─────────┘                   └─────────┘

2. try, throw, catch

Basic Syntax

#include <iostream>
#include <string>

double divide(double a, double b) {
    if (b == 0) {
        throw std::string("Cannot divide by zero");  // Throw exception
    }
    return a / b;
}

int main() {
    try {
        std::cout << divide(10, 2) << std::endl;  // 5
        std::cout << divide(10, 0) << std::endl;  // Exception thrown!
        std::cout << "This line won't execute" << std::endl;
    }
    catch (const std::string& e) {
        std::cout << "Error: " << e << std::endl;
    }

    std::cout << "Program continues" << std::endl;

    return 0;
}

Output:

5
Error: Cannot divide by zero
Program continues

Multiple catch Blocks

#include <iostream>
#include <stdexcept>

void process(int value) {
    if (value < 0) {
        throw std::invalid_argument("Negative numbers not allowed");
    }
    if (value > 100) {
        throw std::out_of_range("Cannot exceed 100");
    }
    if (value == 0) {
        throw 0;  // int type exception
    }
    std::cout << "Value: " << value << std::endl;
}

int main() {
    int tests[] = {50, -10, 150, 0};

    for (int val : tests) {
        try {
            process(val);
        }
        catch (const std::invalid_argument& e) {
            std::cout << "Invalid argument: " << e.what() << std::endl;
        }
        catch (const std::out_of_range& e) {
            std::cout << "Out of range: " << e.what() << std::endl;
        }
        catch (int e) {
            std::cout << "Integer exception: " << e << std::endl;
        }
        catch (...) {  // Catch all exceptions
            std::cout << "Unknown exception" << std::endl;
        }
    }

    return 0;
}

Output:

Value: 50
Invalid argument: Negative numbers not allowed
Out of range: Cannot exceed 100
Integer exception: 0

3. Standard Exception Classes

                std::exception
                      │
       ┌──────────────┼──────────────┐
       ▼              ▼              ▼
  logic_error    runtime_error   bad_alloc
       │              │
   ┌───┴───┐      ┌───┴───┐
   ▼       ▼      ▼       ▼
invalid_  out_of_ overflow_ underflow_
argument  range   error     error

Main Exception Classes

#include <iostream>
#include <stdexcept>
#include <vector>
#include <new>

int main() {
    // logic_error family (programmer mistakes)
    try {
        throw std::invalid_argument("Invalid argument");
    } catch (const std::exception& e) {
        std::cout << "invalid_argument: " << e.what() << std::endl;
    }

    try {
        throw std::out_of_range("Out of range");
    } catch (const std::exception& e) {
        std::cout << "out_of_range: " << e.what() << std::endl;
    }

    try {
        throw std::length_error("Length error");
    } catch (const std::exception& e) {
        std::cout << "length_error: " << e.what() << std::endl;
    }

    // runtime_error family (runtime errors)
    try {
        throw std::runtime_error("Runtime error");
    } catch (const std::exception& e) {
        std::cout << "runtime_error: " << e.what() << std::endl;
    }

    try {
        throw std::overflow_error("Overflow");
    } catch (const std::exception& e) {
        std::cout << "overflow_error: " << e.what() << std::endl;
    }

    // bad_alloc (memory allocation failure)
    try {
        throw std::bad_alloc();
    } catch (const std::exception& e) {
        std::cout << "bad_alloc: " << e.what() << std::endl;
    }

    return 0;
}

Inheriting exception Class

#include <iostream>
#include <exception>
#include <string>

// Custom exception class
class FileNotFoundError : public std::exception {
private:
    std::string message;

public:
    FileNotFoundError(const std::string& filename)
        : message("File not found: " + filename) {}

    const char* what() const noexcept override {
        return message.c_str();
    }
};

class InvalidFormatError : public std::exception {
private:
    std::string message;

public:
    InvalidFormatError(const std::string& detail)
        : message("Invalid format: " + detail) {}

    const char* what() const noexcept override {
        return message.c_str();
    }
};

void readConfig(const std::string& filename) {
    if (filename.empty()) {
        throw FileNotFoundError("(empty filename)");
    }
    if (filename.find(".cfg") == std::string::npos) {
        throw InvalidFormatError("Extension must be .cfg");
    }
    std::cout << filename << " read successfully" << std::endl;
}

int main() {
    std::string files[] = {"", "data.txt", "config.cfg"};

    for (const auto& f : files) {
        try {
            readConfig(f);
        }
        catch (const FileNotFoundError& e) {
            std::cout << "[File Error] " << e.what() << std::endl;
        }
        catch (const InvalidFormatError& e) {
            std::cout << "[Format Error] " << e.what() << std::endl;
        }
    }

    return 0;
}

4. Exception Rethrowing and noexcept

Exception Rethrowing

#include <iostream>
#include <stdexcept>

void lowLevel() {
    throw std::runtime_error("Low-level error");
}

void midLevel() {
    try {
        lowLevel();
    }
    catch (const std::exception& e) {
        std::cout << "[Mid-level] Exception detected: " << e.what() << std::endl;
        throw;  // Rethrow exception (propagate upwards)
    }
}

void highLevel() {
    try {
        midLevel();
    }
    catch (const std::exception& e) {
        std::cout << "[High-level] Final handling: " << e.what() << std::endl;
    }
}

int main() {
    highLevel();
    return 0;
}

Output:

[Mid-level] Exception detected: Low-level error
[High-level] Final handling: Low-level error

noexcept Specifier

#include <iostream>

// Guarantees not to throw exceptions
void safeFunction() noexcept {
    // Throwing exception calls std::terminate()
    std::cout << "Safe function" << std::endl;
}

// Conditional noexcept
template<typename T>
void process(T& obj) noexcept(noexcept(obj.doSomething())) {
    obj.doSomething();
}

class Safe {
public:
    void doSomething() noexcept {
        std::cout << "Safe::doSomething" << std::endl;
    }
};

class Unsafe {
public:
    void doSomething() {
        throw std::runtime_error("Error");
    }
};

int main() {
    std::cout << std::boolalpha;

    // Check noexcept
    std::cout << "safeFunction noexcept: "
              << noexcept(safeFunction()) << std::endl;  // true

    Safe s;
    Unsafe u;

    std::cout << "Safe noexcept: "
              << noexcept(process(s)) << std::endl;    // true
    std::cout << "Unsafe noexcept: "
              << noexcept(process(u)) << std::endl;    // false

    safeFunction();

    return 0;
}

5. Exception Safety

Exception Safety Levels

Level Description
No-throw Never throws exceptions
Strong Restores original state on exception
Basic Maintains valid state after exception
No guarantee Undefined state on exception

RAII for Exception Safety

#include <iostream>
#include <memory>
#include <stdexcept>

// RAII class
class FileHandler {
private:
    FILE* file;

public:
    FileHandler(const char* filename, const char* mode) {
        file = fopen(filename, mode);
        if (!file) {
            throw std::runtime_error("Failed to open file");
        }
        std::cout << "File opened" << std::endl;
    }

    ~FileHandler() {
        if (file) {
            fclose(file);
            std::cout << "File closed" << std::endl;
        }
    }

    void write(const char* data) {
        if (fputs(data, file) == EOF) {
            throw std::runtime_error("Write failed");
        }
    }

    // Delete copy
    FileHandler(const FileHandler&) = delete;
    FileHandler& operator=(const FileHandler&) = delete;
};

void processFile() {
    FileHandler fh("test.txt", "w");  // RAII: open in constructor
    fh.write("Hello, World!\n");
    throw std::runtime_error("Exception in middle!");
    fh.write("This line won't execute");
}  // RAII: automatically closed in destructor

int main() {
    try {
        processFile();
    }
    catch (const std::exception& e) {
        std::cout << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

Output:

File opened
File closed
Exception: Exception in middle!

6. File I/O Basics

File Stream Classes

Class Purpose
ifstream Read files
ofstream Write files
fstream Read/write
#include <iostream>
#include <fstream>
#include <string>

int main() {
    // Write file
    std::ofstream outFile("example.txt");
    if (outFile.is_open()) {
        outFile << "Hello, File!" << std::endl;
        outFile << "Line 2" << std::endl;
        outFile << 42 << " " << 3.14 << std::endl;
        outFile.close();
        std::cout << "File write complete" << std::endl;
    }

    // Read file
    std::ifstream inFile("example.txt");
    if (inFile.is_open()) {
        std::string line;
        while (std::getline(inFile, line)) {
            std::cout << "Read: " << line << std::endl;
        }
        inFile.close();
    }

    return 0;
}

File Open Modes

#include <iostream>
#include <fstream>

int main() {
    // Write mode (default: overwrite)
    std::ofstream f1("test.txt");
    f1 << "New content" << std::endl;
    f1.close();

    // Append mode
    std::ofstream f2("test.txt", std::ios::app);
    f2 << "Appended content" << std::endl;
    f2.close();

    // Binary mode
    std::ofstream f3("data.bin", std::ios::binary);
    int num = 12345;
    f3.write(reinterpret_cast<char*>(&num), sizeof(num));
    f3.close();

    // Read+write mode
    std::fstream f4("test.txt", std::ios::in | std::ios::out);

    // Start at end (append)
    std::ofstream f5("test.txt", std::ios::ate);

    // Truncate existing content
    std::ofstream f6("test.txt", std::ios::trunc);

    return 0;
}
Mode Description
ios::in Read
ios::out Write
ios::app Append to end
ios::ate Start at end
ios::trunc Delete existing content
ios::binary Binary mode

7. File Reading Methods

Various Reading Methods

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>

int main() {
    // Create test file
    std::ofstream out("data.txt");
    out << "Alice 25 90.5\n";
    out << "Bob 30 85.0\n";
    out << "Charlie 28 92.3\n";
    out.close();

    // Method 1: >> operator (whitespace separated)
    std::ifstream f1("data.txt");
    std::string name;
    int age;
    double score;
    std::cout << "=== >> operator ===" << std::endl;
    while (f1 >> name >> age >> score) {
        std::cout << name << ", " << age << ", " << score << std::endl;
    }
    f1.close();

    // Method 2: getline (line by line)
    std::ifstream f2("data.txt");
    std::string line;
    std::cout << "\n=== getline ===" << std::endl;
    while (std::getline(f2, line)) {
        std::cout << "Line: " << line << std::endl;
    }
    f2.close();

    // Method 3: getline + stringstream
    std::ifstream f3("data.txt");
    std::cout << "\n=== stringstream ===" << std::endl;
    while (std::getline(f3, line)) {
        std::istringstream iss(line);
        iss >> name >> age >> score;
        std::cout << "Name=" << name << ", Age=" << age
                  << ", Score=" << score << std::endl;
    }
    f3.close();

    // Method 4: Read entire file
    std::ifstream f4("data.txt");
    std::stringstream buffer;
    buffer << f4.rdbuf();
    std::string content = buffer.str();
    std::cout << "\n=== Full content ===" << std::endl;
    std::cout << content;
    f4.close();

    return 0;
}

Character-wise Reading

#include <iostream>
#include <fstream>

int main() {
    std::ofstream out("chars.txt");
    out << "ABC\nDEF";
    out.close();

    std::ifstream in("chars.txt");
    char c;

    // get() one character at a time
    std::cout << "Character by character: ";
    while (in.get(c)) {
        if (c == '\n') {
            std::cout << "[LF]";
        } else {
            std::cout << c;
        }
    }
    std::cout << std::endl;

    // peek() to preview
    in.clear();
    in.seekg(0);

    std::cout << "Peek: ";
    while (in.peek() != EOF) {
        char peeked = in.peek();
        char got;
        in.get(got);
        std::cout << "(" << (int)peeked << ")";
    }
    std::cout << std::endl;

    in.close();
    return 0;
}

8. Binary Files

Binary Read/Write

#include <iostream>
#include <fstream>
#include <vector>

struct Record {
    int id;
    char name[50];
    double score;
};

int main() {
    // Binary write
    std::ofstream out("records.bin", std::ios::binary);

    Record r1 = {1, "Alice", 95.5};
    Record r2 = {2, "Bob", 87.0};
    Record r3 = {3, "Charlie", 91.2};

    out.write(reinterpret_cast<char*>(&r1), sizeof(Record));
    out.write(reinterpret_cast<char*>(&r2), sizeof(Record));
    out.write(reinterpret_cast<char*>(&r3), sizeof(Record));
    out.close();

    std::cout << "Record size: " << sizeof(Record) << " bytes" << std::endl;

    // Binary read
    std::ifstream in("records.bin", std::ios::binary);

    Record record;
    std::cout << "\n=== Reading records ===" << std::endl;
    while (in.read(reinterpret_cast<char*>(&record), sizeof(Record))) {
        std::cout << "ID: " << record.id
                  << ", Name: " << record.name
                  << ", Score: " << record.score << std::endl;
    }
    in.close();

    // Random access to specific record
    std::ifstream in2("records.bin", std::ios::binary);

    // Move to second record (0-indexed)
    in2.seekg(1 * sizeof(Record));
    in2.read(reinterpret_cast<char*>(&record), sizeof(Record));
    std::cout << "\nSecond record: " << record.name << std::endl;

    in2.close();

    return 0;
}

Save/Load Vector

#include <iostream>
#include <fstream>
#include <vector>

void saveVector(const std::string& filename, const std::vector<int>& vec) {
    std::ofstream out(filename, std::ios::binary);

    // Save size first
    size_t size = vec.size();
    out.write(reinterpret_cast<char*>(&size), sizeof(size));

    // Save data
    out.write(reinterpret_cast<const char*>(vec.data()),
              size * sizeof(int));
    out.close();
}

std::vector<int> loadVector(const std::string& filename) {
    std::ifstream in(filename, std::ios::binary);

    // Read size
    size_t size;
    in.read(reinterpret_cast<char*>(&size), sizeof(size));

    // Read data
    std::vector<int> vec(size);
    in.read(reinterpret_cast<char*>(vec.data()),
            size * sizeof(int));
    in.close();

    return vec;
}

int main() {
    std::vector<int> original = {10, 20, 30, 40, 50};

    saveVector("vector.bin", original);
    std::cout << "Save complete" << std::endl;

    std::vector<int> loaded = loadVector("vector.bin");
    std::cout << "Loaded data: ";
    for (int n : loaded) {
        std::cout << n << " ";
    }
    std::cout << std::endl;

    return 0;
}

9. File Position Control

seekg, seekp, tellg, tellp

#include <iostream>
#include <fstream>

int main() {
    // Create file
    std::ofstream out("position.txt");
    out << "0123456789ABCDEF";
    out.close();

    // Read position control
    std::ifstream in("position.txt");

    // Check current position
    std::cout << "Start position: " << in.tellg() << std::endl;

    // Move to position 5 (from beginning)
    in.seekg(5, std::ios::beg);
    char c;
    in.get(c);
    std::cout << "Character at position 5: " << c << std::endl;

    // Move 3 positions forward from current
    in.seekg(3, std::ios::cur);
    in.get(c);
    std::cout << "3 positions forward: " << c << std::endl;

    // 2 positions before end
    in.seekg(-2, std::ios::end);
    in.get(c);
    std::cout << "2 before end: " << c << std::endl;

    in.close();

    // Write position control
    std::fstream file("position.txt", std::ios::in | std::ios::out);

    file.seekp(10);  // Move to position 10
    file << "XYZ";   // Overwrite ABC with XYZ

    file.seekg(0);   // To beginning
    std::string content;
    std::getline(file, content);
    std::cout << "After modification: " << content << std::endl;

    file.close();

    return 0;
}

Get File Size

#include <iostream>
#include <fstream>

long getFileSize(const std::string& filename) {
    std::ifstream file(filename, std::ios::binary | std::ios::ate);
    if (!file.is_open()) {
        return -1;
    }
    return file.tellg();
}

int main() {
    // Create test file
    std::ofstream out("size_test.txt");
    out << "Hello, World!";
    out.close();

    long size = getFileSize("size_test.txt");
    std::cout << "File size: " << size << " bytes" << std::endl;

    return 0;
}

10. Stream State Checking

State Flags

#include <iostream>
#include <fstream>
#include <sstream>

void checkStreamState(std::ios& stream) {
    std::cout << "good(): " << stream.good() << std::endl;
    std::cout << "eof():  " << stream.eof() << std::endl;
    std::cout << "fail(): " << stream.fail() << std::endl;
    std::cout << "bad():  " << stream.bad() << std::endl;
}

int main() {
    std::cout << std::boolalpha;

    // Normal state
    std::istringstream ss1("100");
    int num;
    ss1 >> num;
    std::cout << "=== After normal read ===" << std::endl;
    checkStreamState(ss1);

    // EOF state
    ss1 >> num;
    std::cout << "\n=== After EOF ===" << std::endl;
    checkStreamState(ss1);

    // Failed state
    std::istringstream ss2("abc");
    ss2 >> num;
    std::cout << "\n=== Invalid format ===" << std::endl;
    checkStreamState(ss2);

    // State reset
    ss2.clear();
    std::cout << "\n=== After clear() ===" << std::endl;
    checkStreamState(ss2);

    // File open failure
    std::ifstream file("nonexistent.txt");
    std::cout << "\n=== Non-existent file ===" << std::endl;
    checkStreamState(file);

    return 0;
}

Enable Exceptions

#include <iostream>
#include <fstream>

int main() {
    std::ifstream file;

    // Enable stream exceptions
    file.exceptions(std::ifstream::failbit | std::ifstream::badbit);

    try {
        file.open("nonexistent_file.txt");
        // Exception thrown if file doesn't exist
    }
    catch (const std::ios_base::failure& e) {
        std::cout << "Failed to open file: " << e.what() << std::endl;
    }

    return 0;
}

11. String Streams

stringstream Usage

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

int main() {
    // String -> number conversion
    std::string numStr = "42 3.14 100";
    std::istringstream iss(numStr);

    int i;
    double d;
    int j;
    iss >> i >> d >> j;
    std::cout << "Parsed: " << i << ", " << d << ", " << j << std::endl;

    // Number -> string conversion
    std::ostringstream oss;
    oss << "Result: " << 123 << " + " << 456 << " = " << (123 + 456);
    std::string result = oss.str();
    std::cout << result << std::endl;

    // CSV parsing
    std::string csv = "Alice,25,90.5";
    std::istringstream csvStream(csv);
    std::string token;
    std::vector<std::string> tokens;

    while (std::getline(csvStream, token, ',')) {
        tokens.push_back(token);
    }

    std::cout << "CSV parsed: ";
    for (const auto& t : tokens) {
        std::cout << "[" << t << "] ";
    }
    std::cout << std::endl;

    // stringstream reuse
    std::stringstream ss;
    ss << "Hello";
    std::cout << "1: " << ss.str() << std::endl;

    ss.str("");  // Clear content
    ss.clear();  // Reset state
    ss << "World";
    std::cout << "2: " << ss.str() << std::endl;

    return 0;
}

12. Practical Examples

Config File Parser

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <map>

class ConfigParser {
private:
    std::map<std::string, std::string> config;

public:
    bool load(const std::string& filename) {
        std::ifstream file(filename);
        if (!file.is_open()) {
            return false;
        }

        std::string line;
        while (std::getline(file, line)) {
            // Skip empty lines and comments
            if (line.empty() || line[0] == '#') continue;

            // Split by '='
            size_t pos = line.find('=');
            if (pos != std::string::npos) {
                std::string key = line.substr(0, pos);
                std::string value = line.substr(pos + 1);

                // Simple whitespace removal
                config[key] = value;
            }
        }
        return true;
    }

    std::string get(const std::string& key,
                    const std::string& defaultValue = "") const {
        auto it = config.find(key);
        if (it != config.end()) {
            return it->second;
        }
        return defaultValue;
    }

    int getInt(const std::string& key, int defaultValue = 0) const {
        auto it = config.find(key);
        if (it != config.end()) {
            return std::stoi(it->second);
        }
        return defaultValue;
    }

    void display() const {
        for (const auto& [key, value] : config) {
            std::cout << key << " = " << value << std::endl;
        }
    }
};

int main() {
    // Create config file
    std::ofstream out("config.ini");
    out << "# Server configuration\n";
    out << "host=localhost\n";
    out << "port=8080\n";
    out << "max_connections=100\n";
    out << "debug=true\n";
    out.close();

    // Read config file
    ConfigParser config;
    if (config.load("config.ini")) {
        std::cout << "=== Config file ===" << std::endl;
        config.display();

        std::cout << "\n=== Individual access ===" << std::endl;
        std::cout << "Host: " << config.get("host") << std::endl;
        std::cout << "Port: " << config.getInt("port") << std::endl;
        std::cout << "Timeout: " << config.getInt("timeout", 30) << std::endl;
    }

    return 0;
}

CSV File Processing

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>

struct Student {
    std::string name;
    int age;
    double score;
};

class CSVHandler {
public:
    static void write(const std::string& filename,
                      const std::vector<Student>& students) {
        std::ofstream file(filename);

        // Header
        file << "name,age,score\n";

        // Data
        for (const auto& s : students) {
            file << s.name << "," << s.age << "," << s.score << "\n";
        }
    }

    static std::vector<Student> read(const std::string& filename) {
        std::vector<Student> students;
        std::ifstream file(filename);

        std::string line;
        std::getline(file, line);  // Skip header

        while (std::getline(file, line)) {
            std::istringstream iss(line);
            Student s;
            std::string field;

            std::getline(iss, s.name, ',');
            std::getline(iss, field, ',');
            s.age = std::stoi(field);
            std::getline(iss, field, ',');
            s.score = std::stod(field);

            students.push_back(s);
        }

        return students;
    }
};

int main() {
    // Write CSV
    std::vector<Student> students = {
        {"Alice", 20, 95.5},
        {"Bob", 22, 87.0},
        {"Charlie", 21, 91.2}
    };

    CSVHandler::write("students.csv", students);
    std::cout << "CSV saved" << std::endl;

    // Read CSV
    auto loaded = CSVHandler::read("students.csv");

    std::cout << "\n=== Student list ===" << std::endl;
    for (const auto& s : loaded) {
        std::cout << s.name << " (" << s.age << " years old): "
                  << s.score << " points" << std::endl;
    }

    return 0;
}

13. Summary

Concept Description
try-catch Exception handling block
throw Throw exception
noexcept Guarantee not to throw
std::exception Standard exception base class
ifstream File read stream
ofstream File write stream
fstream Read/write stream
stringstream String stream
seekg/seekp Move file position
tellg/tellp Check current position

14. Exercises

Exercise 1: Log File Class

Write a Logger class that records messages with date/time.

Exercise 2: Exception Hierarchy

Design a database-related exception class hierarchy. (ConnectionError, QueryError, AuthenticationError, etc.)

Exercise 3: JSON Parser (Simple Version)

Write a class that parses simple key-value JSON. (Example: {"name": "Alice", "age": 25})


Next Steps

Let's learn about smart pointers in 14_Smart_Pointers_Memory.md!

to navigate between lessons