File I/O and Exception Handling: Managing Data and Errors

File Streams: Your Program's Filing Cabinet

File I/O in C++ is like having a filing system - you can read documents (input), write new ones (output), or do both! Streams are the pipelines that connect your program to files.

File Stream Classes

graph TD A[ios_base] --> B[ios] B --> C[istream] B --> D[ostream] C --> E[ifstream
Input File Stream] D --> F[ofstream
Output File Stream] C --> G[iostream] D --> G G --> H[fstream
Input/Output File Stream] style E fill:#E8F5E9 style F fill:#FFE4B5 style H fill:#F3E5F5

Basic File Operations

#include 
#include 
#include 
using namespace std;

// Writing to a file
void writeToFile() {
    ofstream outFile("output.txt");
    
    if (!outFile) {
        cerr << "Error opening file for writing!" << endl;
        return;
    }
    
    outFile << "Hello, File I/O!" << endl;
    outFile << "Line 2" << endl;
    outFile << "The answer is: " << 42 << endl;
    
    outFile.close(); // Optional - destructor closes automatically
}

// Reading from a file
void readFromFile() {
    ifstream inFile("input.txt");
    
    if (!inFile.is_open()) {
        cerr << "Error opening file for reading!" << endl;
        return;
    }
    
    string line;
    while (getline(inFile, line)) {
        cout << "Read: " << line << endl;
    }
    
    inFile.close();
}

// Reading different data types
void readStructuredData() {
    ifstream inFile("data.txt");
    
    string name;
    int age;
    double salary;
    
    while (inFile >> name >> age >> salary) {
        cout << name << " is " << age << " years old, earns $" 
             << salary << endl;
    }
}

// Binary file I/O
void binaryFileOperations() {
    // Writing binary data
    ofstream binOut("data.bin", ios::binary);
    
    int numbers[] = {1, 2, 3, 4, 5};
    binOut.write(reinterpret_cast(numbers), sizeof(numbers));
    binOut.close();
    
    // Reading binary data
    ifstream binIn("data.bin", ios::binary);
    int readNumbers[5];
    binIn.read(reinterpret_cast(readNumbers), sizeof(readNumbers));
    
    for (int n : readNumbers) {
        cout << n << " ";
    }
}

File Opening Modes

File opening modes are like different ways to open a door - you can open it just to look (read), to put things in (write), or both!

File Opening Modes ios::in Open for reading (default for ifstream) ios::out Open for writing (overwrites existing) ios::app Append to end (preserves existing) ios::binary Binary mode (no text formatting) ios::trunc Truncate if exists (delete contents) ios::ate At end initially (can move pointer) Combining Modes: fstream file("data.txt", ios::in | ios::out | ios::binary);

File Mode Examples

// Different ways to open files
ofstream file1("output.txt");                    // Default: ios::out
ofstream file2("output.txt", ios::app);         // Append mode
ofstream file3("output.txt", ios::out | ios::trunc); // Explicit truncate

fstream file4("data.txt", ios::in | ios::out);  // Read and write
fstream file5("binary.dat", ios::binary | ios::in | ios::out);

// Checking if file opened successfully
ifstream inFile("maybe_exists.txt");
if (inFile.is_open()) {
    // File opened successfully
} else {
    // Handle error
}

// Alternative error checking
if (!inFile) {
    cerr << "Failed to open file!" << endl;
}

// Using exceptions for file errors
ifstream exFile;
exFile.exceptions(ifstream::failbit | ifstream::badbit);
try {
    exFile.open("important.txt");
    // Read file...
} catch (const ifstream::failure& e) {
    cerr << "Exception opening file: " << e.what() << endl;
}

File Position and Seeking

File pointers are like bookmarks - they remember where you are in a file and let you jump to specific locations!

Exception Handling: Safety Net for Your Code

Exceptions are like smoke detectors - they alert you when something goes wrong so you can handle it gracefully instead of letting your program crash!

graph TD A[Normal Execution] --> B{Error Occurs?} B -->|No| C[Continue] B -->|Yes| D[Throw Exception] D --> E[Search for Handler] E --> F{Handler Found?} F -->|Yes| G[Execute catch block] F -->|No| H[Propagate up] G --> I[Continue or Exit] H --> J[Program Terminates] style D fill:#F44336 style G fill:#4CAF50 style J fill:#F44336

Exception Handling Basics

// Basic try-catch structure
try {
    // Code that might throw an exception
    int result = riskyOperation();
    cout << "Success: " << result << endl;
} 
catch (const exception& e) {
    // Handle the exception
    cerr << "Error: " << e.what() << endl;
}

// Multiple catch blocks
try {
    processFile("data.txt");
} 
catch (const ifstream::failure& e) {
    cerr << "File error: " << e.what() << endl;
}
catch (const runtime_error& e) {
    cerr << "Runtime error: " << e.what() << endl;
}
catch (const exception& e) {
    cerr << "General error: " << e.what() << endl;
}
catch (...) {
    cerr << "Unknown error occurred!" << endl;
}

// Throwing exceptions
void validateAge(int age) {
    if (age < 0) {
        throw invalid_argument("Age cannot be negative");
    }
    if (age > 150) {
        throw out_of_range("Age seems unrealistic");
    }
}

// Custom exception class
class FileFormatException : public runtime_error {
private:
    int lineNumber;
    
public:
    FileFormatException(const string& msg, int line) 
        : runtime_error(msg), lineNumber(line) {}
    
    int getLineNumber() const { return lineNumber; }
};

Standard Exception Hierarchy

C++ Exception Class Hierarchy exception logic_error runtime_error invalid_argument length_error out_of_range domain_error range_error overflow_error underflow_error system_error bad_alloc

RAII and Exception Safety

RAII (Resource Acquisition Is Initialization) is like having automatic cleanup - resources are tied to object lifetime, so they're always properly released!

Exception Safety and File I/O

// RAII file wrapper
class SafeFile {
private:
    FILE* file;
    
public:
    SafeFile(const string& filename, const string& mode) {
        file = fopen(filename.c_str(), mode.c_str());
        if (!file) {
            throw runtime_error("Failed to open file: " + filename);
        }
    }
    
    ~SafeFile() {
        if (file) {
            fclose(file);
        }
    }
    
    // Delete copy operations
    SafeFile(const SafeFile&) = delete;
    SafeFile& operator=(const SafeFile&) = delete;
    
    // Move operations
    SafeFile(SafeFile&& other) noexcept : file(other.file) {
        other.file = nullptr;
    }
    
    FILE* get() { return file; }
};

// Exception-safe file processing
void processFilesSafely(const string& input, const string& output) {
    ifstream inFile(input);
    if (!inFile) {
        throw runtime_error("Cannot open input file");
    }
    
    ofstream outFile(output);
    if (!outFile) {
        throw runtime_error("Cannot open output file");
    }
    
    try {
        string line;
        int lineNum = 0;
        
        while (getline(inFile, line)) {
            lineNum++;
            
            // Process line - might throw
            string processed = processLine(line);
            
            outFile << processed << endl;
            if (!outFile) {
                throw runtime_error("Write failed at line " + 
                                  to_string(lineNum));
            }
        }
    }
    catch (...) {
        // Files automatically closed by destructors
        throw; // Re-throw the exception
    }
    // Files automatically closed here too
}

Practical File Processing Example

Let's build a complete example that processes CSV files with proper error handling!

class CSVProcessor {
private:
    string filename;
    char delimiter;
    
public:
    CSVProcessor(const string& file, char delim = ',') 
        : filename(file), delimiter(delim) {}
    
    vector> readCSV() {
        vector> data;
        ifstream file(filename);
        
        if (!file) {
            throw runtime_error("Cannot open file: " + filename);
        }
        
        string line;
        int lineNum = 0;
        
        while (getline(file, line)) {
            lineNum++;
            vector row = parseLine(line, lineNum);
            data.push_back(row);
        }
        
        return data;
    }
    
private:
    vector parseLine(const string& line, int lineNum) {
        vector result;
        stringstream ss(line);
        string item;
        
        while (getline(ss, item, delimiter)) {
            // Trim whitespace
            item.erase(0, item.find_first_not_of(" \t"));
            item.erase(item.find_last_not_of(" \t") + 1);
            
            result.push_back(item);
        }
        
        if (result.empty()) {
            throw runtime_error("Empty line at line " + 
                              to_string(lineNum));
        }
        
        return result;
    }
};

// Usage with exception handling
int main() {
    try {
        CSVProcessor processor("data.csv");
        auto data = processor.readCSV();
        
        // Process data
        for (const auto& row : data) {
            for (const auto& field : row) {
                cout << field << " | ";
            }
            cout << endl;
        }
    }
    catch (const runtime_error& e) {
        cerr << "Error: " << e.what() << endl;
        return 1;
    }
    catch (const exception& e) {
        cerr << "Unexpected error: " << e.what() << endl;
        return 2;
    }
    
    return 0;
}

Practice Exercise: Log File Analyzer

Build a Log File Analyzer

Create a program that analyzes server log files with these features:

  1. Parse log entries with timestamp, level, and message
  2. Filter by date range and severity level
  3. Generate statistics (errors per hour, etc.)
  4. Handle corrupted log entries gracefully
  5. Export results to a report file
struct LogEntry {
    time_t timestamp;
    string level;  // INFO, WARNING, ERROR
    string message;
};

class LogAnalyzer {
private:
    vector entries;
    
public:
    void loadLogFile(const string& filename);
    void filterByDateRange(time_t start, time_t end);
    void filterByLevel(const string& level);
    
    map getLevelCounts();
    map getErrorsByHour();
    
    void exportReport(const string& filename);
    
private:
    LogEntry parseLogLine(const string& line);
    // TODO: Implement parsing with exception handling
};
Implementation Hints

Advanced File I/O Techniques

graph TD A[Advanced File I/O] --> B[Memory Mapping] A --> C[Buffering Control] A --> D[File Locking] A --> E[Async I/O] B --> F["mmap on Unix\nMapViewOfFile on Windows"] C --> G["setvbuf()\nCustom buffers"] D --> H["Platform specific\nflock/LockFile"] E --> I["C++20 coroutines\nPlatform APIs"]

Performance Optimization

// Custom buffering for better performance
class BufferedFileWriter {
private:
    ofstream file;
    vector buffer;
    size_t bufferSize;
    size_t currentPos;
    
public:
    BufferedFileWriter(const string& filename, size_t bufSize = 65536)
        : bufferSize(bufSize), currentPos(0) {
        buffer.resize(bufferSize);
        file.open(filename, ios::binary);
        if (!file) {
            throw runtime_error("Cannot open file for writing");
        }
    }
    
    ~BufferedFileWriter() {
        flush();
    }
    
    void write(const string& data) {
        for (char c : data) {
            buffer[currentPos++] = c;
            if (currentPos >= bufferSize) {
                flush();
            }
        }
    }
    
    void flush() {
        if (currentPos > 0) {
            file.write(buffer.data(), currentPos);
            currentPos = 0;
        }
    }
};

// Memory-mapped file reading (platform specific)
#ifdef _WIN32
    // Windows implementation
#else
    // Unix implementation using mmap
#endif

Exception Safety Guarantees

Exception Safety Levels Basic Guarantee No resources leaked, objects in valid state Strong Guarantee Operation succeeds or no changes (commit or rollback) No-throw Guarantee Operation never throws (destructors, swap)

Challenge Exercise: Database File Manager

Advanced File I/O Challenge

Build a simple file-based database with:

  1. Binary file format with index
  2. Transaction support (commit/rollback)
  3. Concurrent access handling
  4. Crash recovery
  5. Comprehensive error handling
Design Hints

Key Takeaways

graph LR A[Master File I/O] --> B[Handle Errors Gracefully] B --> C[Build Robust Applications] C --> D[Process Data Reliably] D --> E[Professional C++ Developer!]