C++ File Handling Input/Output Operations
Data Persistence

C++ File Handling: Complete Guide with Examples

Master C++ file handling: fstream, ofstream, ifstream operations. Learn text/binary file operations, file pointers, error handling, serialization, and best practices.

fstream

Input & Output

ifstream

Input Only

ofstream

Output Only

Binary Files

Raw Data

Introduction to File Handling

File handling in C++ allows programs to store data permanently on disk, read existing data, and perform various file operations. It's essential for data persistence, configuration storage, and data exchange between programs.

Why File Handling?
  • Data persistence beyond program execution
  • Configuration and settings storage
  • Data exchange between programs
  • Logging and audit trails
  • Database and large data storage
  • Backup and recovery systems
File Stream Classes
  • fstream: Input and output operations
  • ifstream: Input (reading) operations only
  • ofstream: Output (writing) operations only
  • ios: Base class for all stream classes
  • Header: #include <fstream>
File Handling Process Flow
1
Open File: Create file stream object and open file
2
Check Status: Verify file opened successfully
3
Read/Write: Perform file operations
4
Error Handling: Check for errors during operations
5
Close File: Release file resources

C++ File Stream Classes

The following table compares all file stream classes in C++ with their purposes, methods, and characteristics:

Class Purpose Key Methods Use Cases
fstream Both input and output operations open(), close(), read(), write(), seekg(), seekp() Read/write operations, file updates
ifstream Input operations only (reading) open(), close(), get(), getline(), read(), eof() Reading configuration, data processing
ofstream Output operations only (writing) open(), close(), put(), write(), flush() Logging, data storage, report generation
File Modes Specify how file is opened ios::in, ios::out, ios::app, ios::binary, ios::trunc Control file access behavior
Text Files Human-readable data <<, >>, getline() Configuration files, logs, CSV data
Binary Files Raw data storage read(), write() Images, databases, serialized objects

1. File Opening Modes

File modes determine how a file is opened and what operations are allowed. They control whether to read, write, append, create new files, or handle binary data.

File Mode Flags
// Common file mode flags:
ios::in      // Open for reading (input)
ios::out     // Open for writing (output)
ios::app     // Append to end of file
ios::ate     // Open and seek to end
ios::trunc   // Truncate file if it exists
ios::binary  // Open in binary mode

// Combinations:
ios::in | ios::out      // Read and write
ios::out | ios::trunc   // Write and truncate
ios::out | ios::app     // Write and append
ios::in | ios::binary   // Read binary data
File Mode Examples
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
    cout << "=== File Mode Demonstrations ===" << endl << endl;
    
    // 1. ios::out - Create new file or overwrite existing
    cout << "1. ios::out (write mode):" << endl;
    ofstream outFile("example1.txt", ios::out);
    if (outFile.is_open()) {
        outFile << "This text is written using ios::out mode.\n";
        outFile << "Existing file content would be overwritten.\n";
        outFile.close();
        cout << "File created/overwritten successfully." << endl;
    } else {
        cout << "Error creating file!" << endl;
    }
    cout << endl;
    
    // 2. ios::app - Append to existing file
    cout << "2. ios::app (append mode):" << endl;
    ofstream appendFile("example2.txt", ios::app);
    if (appendFile.is_open()) {
        appendFile << "Line 1: Appended text.\n";
        appendFile << "Line 2: More appended text.\n";
        appendFile.close();
        cout << "Text appended to file." << endl;
    }
    cout << endl;
    
    // 3. ios::in - Read from file
    cout << "3. ios::in (read mode):" << endl;
    ifstream inFile("example1.txt", ios::in);
    if (inFile.is_open()) {
        string line;
        cout << "Contents of example1.txt:" << endl;
        while (getline(inFile, line)) {
            cout << line << endl;
        }
        inFile.close();
    } else {
        cout << "Error reading file!" << endl;
    }
    cout << endl;
    
    // 4. ios::in | ios::out - Read and write
    cout << "4. ios::in | ios::out (read and write):" << endl;
    fstream rwFile("example3.txt", ios::in | ios::out | ios::trunc);
    if (rwFile.is_open()) {
        // Write some data
        rwFile << "Initial data line 1\n";
        rwFile << "Initial data line 2\n";
        rwFile << "Initial data line 3\n";
        
        // Read back what we wrote
        rwFile.seekg(0); // Move to beginning
        string content;
        cout << "Reading after writing:" << endl;
        while (getline(rwFile, content)) {
            cout << content << endl;
        }
        rwFile.close();
    }
    cout << endl;
    
    // 5. ios::ate - Open and seek to end
    cout << "5. ios::ate (at end mode):" << endl;
    fstream ateFile("example4.txt", ios::in | ios::out | ios::ate);
    if (ateFile.is_open()) {
        // Get current position (should be at end)
        streampos pos = ateFile.tellg();
        cout << "Initial position: " << pos << endl;
        
        // Write more data
        ateFile << "Added with ios::ate mode.\n";
        
        // Go to beginning and read
        ateFile.seekg(0);
        string line;
        cout << "File contents:" << endl;
        while (getline(ateFile, line)) {
            cout << line << endl;
        }
        ateFile.close();
    }
    cout << endl;
    
    // 6. ios::trunc - Truncate if exists
    cout << "6. ios::trunc (truncate mode):" << endl;
    ofstream truncFile("example5.txt", ios::out | ios::trunc);
    if (truncFile.is_open()) {
        truncFile << "This file was truncated if it existed.\n";
        truncFile << "All previous content was removed.\n";
        truncFile.close();
        cout << "File truncated and new data written." << endl;
    }
    cout << endl;
    
    // 7. Default modes for different stream types
    cout << "7. Default file modes:" << endl;
    cout << "ofstream default: ios::out" << endl;
    cout << "ifstream default: ios::in" << endl;
    cout << "fstream default: none (must specify)" << endl;
    
    // Cleanup: Remove created files
    remove("example1.txt");
    remove("example2.txt");
    remove("example3.txt");
    remove("example4.txt");
    remove("example5.txt");
    
    return 0;
}
Mode Selection Guidelines:
  • ios::in | ios::out: Read and write existing file
  • ios::out | ios::trunc: Create new or overwrite
  • ios::out | ios::app: Append without overwriting
  • ios::in | ios::binary: Read binary data
  • ios::ate: Useful for log files
  • Combine modes using bitwise OR (|)
Common Mistakes:
  • Forgetting to check if file opened successfully
  • Using wrong mode for intended operation
  • Not closing files after use
  • Opening non-existent file for reading
  • File permission issues
  • Path/directory doesn't exist

2. Text File Operations

Text files store data in human-readable format. C++ provides various methods for reading and writing text files using stream operators and functions.

Basic Text File Operations
// Writing to text file
ofstream outFile("file.txt");
outFile << "Text data" << endl;
outFile << variable << " " << anotherVar;

// Reading from text file
ifstream inFile("file.txt");
string line;
while(getline(inFile, line)) {
    cout << line << endl;
}

// Reading word by word
string word;
while(inFile >> word) {
    cout << word << endl;
}
Text File Examples
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iomanip>  // For formatted output
using namespace std;

// Student structure for demonstration
struct Student {
    int id;
    string name;
    double gpa;
    string major;
};

int main() {
    cout << "=== Text File Operations ===" << endl << endl;
    
    // 1. Basic write operations
    cout << "1. Basic File Writing:" << endl;
    ofstream writeFile("students.txt");
    
    if (writeFile.is_open()) {
        writeFile << "Student Records Database\n";
        writeFile << "=======================\n\n";
        writeFile << "ID\tName\t\tGPA\tMajor\n";
        writeFile << "--------------------------------\n";
        
        // Write some student records
        writeFile << 1001 << "\tJohn Doe\t" << fixed << setprecision(2) 
                  << 3.75 << "\tComputer Science\n";
        writeFile << 1002 << "\tJane Smith\t" << 3.92 << "\tMathematics\n";
        writeFile << 1003 << "\tBob Johnson\t" << 3.45 << "\tPhysics\n";
        writeFile << 1004 << "\tAlice Brown\t" << 3.88 << "\tBiology\n";
        
        writeFile.close();
        cout << "Student records written to file." << endl;
    }
    cout << endl;
    
    // 2. Basic read operations
    cout << "2. Reading Entire File:" << endl;
    ifstream readFile("students.txt");
    
    if (readFile.is_open()) {
        string line;
        cout << "Contents of students.txt:" << endl;
        cout << "-------------------------" << endl;
        while (getline(readFile, line)) {
            cout << line << endl;
        }
        readFile.close();
    }
    cout << endl;
    
    // 3. Reading word by word
    cout << "3. Reading Word by Word:" << endl;
    ifstream wordFile("students.txt");
    
    if (wordFile.is_open()) {
        string word;
        int wordCount = 0;
        cout << "First 20 words from file:" << endl;
        while (wordFile >> word && wordCount < 20) {
            cout << "Word " << ++wordCount << ": " << word << endl;
        }
        wordFile.close();
    }
    cout << endl;
    
    // 4. Formatted input with stringstream
    cout << "4. Parsing Structured Data:" << endl;
    ifstream dataFile("students.txt");
    
    if (dataFile.is_open()) {
        string line;
        int lineNum = 0;
        
        // Skip header lines
        for (int i = 0; i < 4; i++) {
            getline(dataFile, line);
        }
        
        vector<Student> students;
        
        // Parse student data
        while (getline(dataFile, line)) {
            if (line.empty()) continue;
            
            stringstream ss(line);
            Student s;
            
            if (ss >> s.id) {
                // Read name (could be multiple words)
                string namePart;
                string fullName;
                while (ss >> namePart && !isdigit(namePart[0]) && namePart.find('.') == string::npos) {
                    if (!fullName.empty()) fullName += " ";
                    fullName += namePart;
                }
                s.name = fullName;
                
                // The last read was GPA (as string)
                s.gpa = stod(namePart);
                
                // Read major
                ss >> s.major;
                
                students.push_back(s);
            }
        }
        
        cout << "Parsed " << students.size() << " student records:" << endl;
        for (const auto& student : students) {
            cout << "ID: " << student.id 
                 << ", Name: " << student.name 
                 << ", GPA: " << student.gpa 
                 << ", Major: " << student.major << endl;
        }
        
        dataFile.close();
    }
    cout << endl;
    
    // 5. CSV file operations
    cout << "5. CSV File Operations:" << endl;
    
    // Write CSV file
    ofstream csvFile("data.csv");
    if (csvFile.is_open()) {
        csvFile << "Name,Age,Salary,Department\n";
        csvFile << "John Doe,30,75000.50,Engineering\n";
        csvFile << "Jane Smith,28,82000.75,Marketing\n";
        csvFile << "Bob Johnson,35,68000.25,Sales\n";
        csvFile << "Alice Brown,32,91000.00,Finance\n";
        csvFile.close();
        cout << "CSV file created." << endl;
    }
    
    // Read and parse CSV
    ifstream readCSV("data.csv");
    if (readCSV.is_open()) {
        string line;
        getline(readCSV, line); // Skip header
        
        cout << "CSV Data:" << endl;
        while (getline(readCSV, line)) {
            stringstream ss(line);
            string token;
            vector<string> tokens;
            
            while (getline(ss, token, ',')) {
                tokens.push_back(token);
            }
            
            if (tokens.size() >= 4) {
                cout << "Name: " << tokens[0] 
                     << ", Age: " << tokens[1]
                     << ", Salary: $" << tokens[2]
                     << ", Dept: " << tokens[3] << endl;
            }
        }
        readCSV.close();
    }
    cout << endl;
    
    // 6. Character by character operations
    cout << "6. Character Operations:" << endl;
    ofstream charFile("char_demo.txt");
    if (charFile.is_open()) {
        for (char c = 'A'; c <= 'Z'; c++) {
            charFile.put(c);  // Write character
            charFile.put(' ');
        }
        charFile.put('\n');
        charFile.close();
    }
    
    ifstream readChar("char_demo.txt");
    if (readChar.is_open()) {
        char ch;
        cout << "Characters from file: ";
        while (readChar.get(ch)) {
            cout << ch;
        }
        readChar.close();
        cout << endl;
    }
    
    // 7. Appending to existing file
    cout << "\n7. Appending to File:" << endl;
    ofstream appendFile("students.txt", ios::app);
    if (appendFile.is_open()) {
        appendFile << "\n--- New Students Added ---\n";
        appendFile << 1005 << "\tCharlie Davis\t" << 3.65 << "\tChemistry\n";
        appendFile << 1006 << "\tDiana Wilson\t" << 3.79 << "\tHistory\n";
        appendFile.close();
        cout << "New students appended to file." << endl;
    }
    
    // Cleanup
    remove("students.txt");
    remove("data.csv");
    remove("char_demo.txt");
    
    return 0;
}

Text File Best Practices

  • Always check if file opened successfully
  • Close files when done to free resources
  • Use getline() for reading entire lines
  • Handle different line endings (Windows vs Unix)
  • Validate data when reading from files
  • Use proper error handling for file operations

3. Binary File Operations

Binary files store data in raw binary format, preserving exact byte representations. They're efficient for storing complex data structures, images, and serialized objects.

Binary File Operations
// Writing binary data
ofstream binFile("data.bin", ios::binary);
int data = 42;
binFile.write((char*)&data, sizeof(data));

// Reading binary data
ifstream readBin("data.bin", ios::binary);
int readData;
readBin.read((char*)&readData, sizeof(readData));

// Working with structures
struct Data {
    int id;
    char name[50];
    double value;
};

Data d = {1, "Example", 3.14};
binFile.write((char*)&d, sizeof(d));
Binary File Examples
#include <iostream>
#include <fstream>
#include <cstring>  // For strcpy
#include <vector>
#include <iomanip>
using namespace std;

// Structure for binary file operations
struct Employee {
    int id;
    char name[50];
    double salary;
    char department[30];
    
    void display() const {
        cout << left << setw(6) << id 
             << setw(25) << name 
             << "$" << setw(12) << fixed << setprecision(2) << salary
             << setw(15) << department << endl;
    }
};

// Structure for array storage
struct InventoryItem {
    int itemId;
    char itemName[50];
    int quantity;
    double price;
};

int main() {
    cout << "=== Binary File Operations ===" << endl << endl;
    
    // 1. Basic binary write/read
    cout << "1. Basic Binary Operations:" << endl;
    
    // Write primitive types
    ofstream outBin("binary_data.bin", ios::binary);
    if (outBin.is_open()) {
        int num = 12345;
        double pi = 3.14159265359;
        char character = 'A';
        bool flag = true;
        
        outBin.write((char*)&num, sizeof(num));
        outBin.write((char*)&pi, sizeof(pi));
        outBin.write((char*)&character, sizeof(character));
        outBin.write((char*)&flag, sizeof(flag));
        
        outBin.close();
        cout << "Primitive types written to binary file." << endl;
    }
    
    // Read primitive types
    ifstream inBin("binary_data.bin", ios::binary);
    if (inBin.is_open()) {
        int readNum;
        double readPi;
        char readChar;
        bool readFlag;
        
        inBin.read((char*)&readNum, sizeof(readNum));
        inBin.read((char*)&readPi, sizeof(readPi));
        inBin.read((char*)&readChar, sizeof(readChar));
        inBin.read((char*)&readFlag, sizeof(readFlag));
        
        cout << "Read from binary file:" << endl;
        cout << "Integer: " << readNum << endl;
        cout << "Double: " << readPi << endl;
        cout << "Char: " << readChar << endl;
        cout << "Bool: " << boolalpha << readFlag << endl;
        
        inBin.close();
    }
    cout << endl;
    
    // 2. Structure binary operations
    cout << "2. Structure Binary Operations:" << endl;
    
    // Create and write employee records
    vector<Employee> employees = {
        {1001, "John Doe", 75000.50, "Engineering"},
        {1002, "Jane Smith", 82000.75, "Marketing"},
        {1003, "Bob Johnson", 68000.25, "Sales"},
        {1004, "Alice Brown", 91000.00, "Finance"},
        {1005, "Charlie Davis", 72000.00, "HR"}
    };
    
    ofstream empFile("employees.bin", ios::binary);
    if (empFile.is_open()) {
        // Write number of records first
        int count = employees.size();
        empFile.write((char*)&count, sizeof(count));
        
        // Write each employee record
        for (const auto& emp : employees) {
            empFile.write((char*)&emp, sizeof(Employee));
        }
        
        empFile.close();
        cout << employees.size() << " employee records written." << endl;
    }
    
    // Read employee records
    ifstream readEmp("employees.bin", ios::binary);
    if (readEmp.is_open()) {
        int recordCount;
        readEmp.read((char*)&recordCount, sizeof(recordCount));
        
        cout << "\nReading " << recordCount << " employee records:" << endl;
        cout << string(60, '-') << endl;
        cout << left << setw(6) << "ID" 
             << setw(25) << "Name" 
             << setw(13) << "Salary" 
             << setw(15) << "Department" << endl;
        cout << string(60, '-') << endl;
        
        vector<Employee> readEmployees;
        readEmployees.reserve(recordCount);
        
        for (int i = 0; i < recordCount; i++) {
            Employee emp;
            readEmp.read((char*)&emp, sizeof(Employee));
            readEmployees.push_back(emp);
            emp.display();
        }
        
        readEmp.close();
        
        // Verify data integrity
        cout << "\nData verification:" << endl;
        cout << "Records read: " << readEmployees.size() << endl;
        cout << "First employee name: " << readEmployees[0].name << endl;
        cout << "Last employee salary: $" << readEmployees.back().salary << endl;
    }
    cout << endl;
    
    // 3. Random access in binary files
    cout << "3. Random Access Operations:" << endl;
    
    fstream randomFile("random_access.bin", ios::binary | ios::in | ios::out | ios::trunc);
    if (randomFile.is_open()) {
        // Write 10 integers
        for (int i = 0; i < 10; i++) {
            int num = (i + 1) * 100;
            randomFile.write((char*)&num, sizeof(num));
        }
        
        // Read specific positions
        cout << "Reading specific positions:" << endl;
        
        // Read 3rd integer (position 2, since 0-based)
        randomFile.seekg(2 * sizeof(int));
        int thirdNum;
        randomFile.read((char*)&thirdNum, sizeof(int));
        cout << "3rd number: " << thirdNum << endl;
        
        // Read 7th integer
        randomFile.seekg(6 * sizeof(int));
        int seventhNum;
        randomFile.read((char*)&seventhNum, sizeof(int));
        cout << "7th number: " << seventhNum << endl;
        
        // Update 5th integer
        randomFile.seekp(4 * sizeof(int));
        int newNum = 999;
        randomFile.write((char*)&newNum, sizeof(newNum));
        
        // Verify update
        randomFile.seekg(4 * sizeof(int));
        int updatedNum;
        randomFile.read((char*)&updatedNum, sizeof(updatedNum));
        cout << "Updated 5th number: " << updatedNum << endl;
        
        randomFile.close();
    }
    cout << endl;
    
    // 4. Binary file with arrays
    cout << "4. Array Operations in Binary Files:" << endl;
    
    InventoryItem inventory[] = {
        {101, "Laptop", 25, 999.99},
        {102, "Mouse", 150, 24.50},
        {103, "Keyboard", 80, 49.99},
        {104, "Monitor", 40, 299.99},
        {105, "Headphones", 120, 79.99}
    };
    
    ofstream invFile("inventory.bin", ios::binary);
    if (invFile.is_open()) {
        // Write entire array
        invFile.write((char*)inventory, sizeof(inventory));
        invFile.close();
        cout << "Inventory array written to binary file." << endl;
    }
    
    // Read array back
    InventoryItem readInventory[5];
    ifstream readInv("inventory.bin", ios::binary);
    if (readInv.is_open()) {
        readInv.read((char*)readInventory, sizeof(readInventory));
        readInv.close();
        
        cout << "\nInventory Items:" << endl;
        cout << string(70, '-') << endl;
        cout << left << setw(8) << "ID" 
             << setw(20) << "Item Name" 
             << setw(12) << "Quantity" 
             << setw(10) << "Price" << endl;
        cout << string(70, '-') << endl;
        
        for (const auto& item : readInventory) {
            cout << left << setw(8) << item.itemId
                 << setw(20) << item.itemName
                 << setw(12) << item.quantity
                 << "$" << fixed << setprecision(2) << item.price << endl;
        }
    }
    cout << endl;
    
    // 5. Binary file size and positioning
    cout << "5. File Size and Positioning:" << endl;
    
    ifstream sizeFile("employees.bin", ios::binary | ios::ate); // Open at end
    if (sizeFile.is_open()) {
        // Get file size
        streampos fileSize = sizeFile.tellg();
        cout << "File size: " << fileSize << " bytes" << endl;
        
        // Calculate number of records
        sizeFile.seekg(0); // Go to beginning
        int recordCount;
        sizeFile.read((char*)&recordCount, sizeof(recordCount));
        
        cout << "Record count from file: " << recordCount << endl;
        
        // Verify file size calculation
        streampos expectedSize = sizeof(int) + (recordCount * sizeof(Employee));
        cout << "Expected size: " << expectedSize << " bytes" << endl;
        cout << "Size match: " << (fileSize == expectedSize ? "Yes" : "No") << endl;
        
        sizeFile.close();
    }
    
    // Cleanup
    remove("binary_data.bin");
    remove("employees.bin");
    remove("random_access.bin");
    remove("inventory.bin");
    
    return 0;
}
Text Files
  • Human readable
  • Larger file size
  • Slower processing
  • Platform dependent line endings
  • Easy to debug
  • Good for configuration
Binary Files
  • Machine readable
  • Smaller file size
  • Faster processing
  • Platform independent
  • Preserves exact data
  • Good for databases

4. File Pointers and Positioning

File pointers track read/write positions in files. C++ provides functions to manipulate these pointers for random access operations.

tellg() & tellp()

Get current position of get/put pointers.

ifstream file("data.txt");
streampos pos = file.tellg();
cout << "Current position: " << pos;
seekg() & seekp()

Set position of get/put pointers.

// Move to position 100
file.seekg(100);

// Move from current position
file.seekg(50, ios::cur);

// Move from end
file.seekg(-20, ios::end);
Positioning Constants

Reference points for seeking.

ios::beg  // Beginning of file
ios::cur  // Current position
ios::end  // End of file

// Examples:
seekg(0, ios::beg);  // Beginning
seekg(0, ios::end);  // End
seekg(-10, ios::cur); // Back 10 bytes
File Pointer Examples
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;

struct Record {
    int id;
    char data[100];
};

int main() {
    cout << "=== File Pointer Operations ===" << endl << endl;
    
    // 1. Basic file pointer operations
    cout << "1. Basic Pointer Operations:" << endl;
    
    fstream file("pointers.dat", ios::binary | ios::in | ios::out | ios::trunc);
    if (file.is_open()) {
        // Write some data
        for (int i = 0; i < 10; i++) {
            int num = i * 10;
            file.write((char*)&num, sizeof(num));
        }
        
        // Get file size
        file.seekg(0, ios::end);
        streampos fileSize = file.tellg();
        cout << "File size: " << fileSize << " bytes" << endl;
        
        // Get current positions
        streampos getPos = file.tellg();
        streampos putPos = file.tellp();
        cout << "Get pointer: " << getPos << endl;
        cout << "Put pointer: " << putPos << endl;
        
        // Move to beginning and read
        file.seekg(0, ios::beg);
        cout << "Numbers in file: ";
        for (int i = 0; i < 10; i++) {
            int num;
            file.read((char*)&num, sizeof(num));
            cout << num << " ";
        }
        cout << endl;
        
        file.close();
    }
    cout << endl;
    
    // 2. Random access with seek operations
    cout << "2. Random Access Operations:" << endl;
    
    fstream randomFile("random.dat", ios::binary | ios::in | ios::out | ios::trunc);
    if (randomFile.is_open()) {
        // Create and write records
        Record records[5];
        for (int i = 0; i < 5; i++) {
            records[i].id = 1000 + i;
            string data = "Record data for ID " + to_string(1000 + i);
            strcpy(records[i].data, data.c_str());
            randomFile.write((char*)&records[i], sizeof(Record));
        }
        
        // Read records in reverse order
        cout << "Reading records in reverse:" << endl;
        for (int i = 4; i >= 0; i--) {
            randomFile.seekg(i * sizeof(Record), ios::beg);
            Record rec;
            randomFile.read((char*)&rec, sizeof(Record));
            cout << "Record at position " << i << ": "
                 << rec.id << " - " << rec.data << endl;
        }
        
        // Update specific record
        cout << "\nUpdating record 2:" << endl;
        randomFile.seekp(2 * sizeof(Record), ios::beg);
        Record updated;
        updated.id = 9999;
        strcpy(updated.data, "UPDATED RECORD DATA");
        randomFile.write((char*)&updated, sizeof(Record));
        
        // Verify update
        randomFile.seekg(2 * sizeof(Record), ios::beg);
        Record verify;
        randomFile.read((char*)&verify, sizeof(Record));
        cout << "Updated record: " << verify.id << " - " << verify.data << endl;
        
        randomFile.close();
    }
    cout << endl;
    
    // 3. Relative positioning
    cout << "3. Relative Positioning:" << endl;
    
    fstream relFile("relative.dat", ios::binary | ios::in | ios::out | ios::trunc);
    if (relFile.is_open()) {
        // Write alphabet
        for (char c = 'A'; c <= 'Z'; c++) {
            relFile.put(c);
        }
        
        // Move around using relative positioning
        relFile.seekg(0, ios::beg); // Start at beginning
        
        // Read first 5 characters
        cout << "First 5 characters: ";
        for (int i = 0; i < 5; i++) {
            char c;
            relFile.get(c);
            cout << c;
        }
        cout << endl;
        
        // Move 10 characters forward from current position
        relFile.seekg(10, ios::cur);
        char c;
        relFile.get(c);
        cout << "Character after moving 10 forward: " << c << endl;
        
        // Move 5 characters back from current position
        relFile.seekg(-5, ios::cur);
        relFile.get(c);
        cout << "Character after moving 5 back: " << c << endl;
        
        // Move to 5 characters from end
        relFile.seekg(-5, ios::end);
        relFile.get(c);
        cout << "5th character from end: " << c << endl;
        
        // Get current position
        streampos pos = relFile.tellg();
        cout << "Final position: " << pos << endl;
        
        relFile.close();
    }
    cout << endl;
    
    // 4. Practical example: Database-style operations
    cout << "4. Database-style Operations:" << endl;
    
    struct Customer {
        int accountNo;
        char name[50];
        double balance;
        bool active;
    };
    
    // Create sample database
    vector<Customer> customers = {
        {1001, "John Doe", 1500.75, true},
        {1002, "Jane Smith", 25000.50, true},
        {1003, "Bob Johnson", 500.25, false},
        {1004, "Alice Brown", 12000.00, true},
        {1005, "Charlie Davis", 750.80, true}
    };
    
    // Write to database file
    fstream dbFile("customers.db", ios::binary | ios::in | ios::out | ios::trunc);
    if (dbFile.is_open()) {
        // Write header with record count
        int count = customers.size();
        dbFile.write((char*)&count, sizeof(count));
        
        // Write each customer
        for (const auto& cust : customers) {
            dbFile.write((char*)&cust, sizeof(Customer));
        }
        
        // Function to display customer
        auto displayCustomer = [](int index, const Customer& c) {
            cout << "Record " << index << ": "
                 << c.accountNo << " | "
                 << c.name << " | $"
                 << c.balance << " | "
                 << (c.active ? "Active" : "Inactive") << endl;
        };
        
        // Read specific records
        cout << "\nReading specific records:" << endl;
        
        // Read record 2 (0-based index 1)
        dbFile.seekg(sizeof(int) + (1 * sizeof(Customer)));
        Customer cust2;
        dbFile.read((char*)&cust2, sizeof(Customer));
        displayCustomer(1, cust2);
        
        // Read record 4 (0-based index 3)
        dbFile.seekg(sizeof(int) + (3 * sizeof(Customer)));
        Customer cust4;
        dbFile.read((char*)&cust4, sizeof(Customer));
        displayCustomer(3, cust4);
        
        // Read last record
        dbFile.seekg(sizeof(int) + ((count - 1) * sizeof(Customer)));
        Customer lastCust;
        dbFile.read((char*)&lastCust, sizeof(Customer));
        displayCustomer(count - 1, lastCust);
        
        // Update a record
        cout << "\nUpdating record 3:" << endl;
        Customer updatedCust = {1003, "Bob Johnson UPDATED", 1000.50, true};
        dbFile.seekp(sizeof(int) + (2 * sizeof(Customer)));
        dbFile.write((char*)&updatedCust, sizeof(Customer));
        
        // Verify update
        dbFile.seekg(sizeof(int) + (2 * sizeof(Customer)));
        Customer verifyCust;
        dbFile.read((char*)&verifyCust, sizeof(Customer));
        displayCustomer(2, verifyCust);
        
        // Calculate total balance
        cout << "\nCalculating statistics:" << endl;
        double totalBalance = 0;
        int activeCount = 0;
        
        dbFile.seekg(sizeof(int)); // Skip count
        for (int i = 0; i < count; i++) {
            Customer c;
            dbFile.read((char*)&c, sizeof(Customer));
            totalBalance += c.balance;
            if (c.active) activeCount++;
        }
        
        cout << "Total balance: $" << totalBalance << endl;
        cout << "Active customers: " << activeCount << endl;
        cout << "Average balance: $" << (totalBalance / count) << endl;
        
        dbFile.close();
    }
    
    // Cleanup
    remove("pointers.dat");
    remove("random.dat");
    remove("relative.dat");
    remove("customers.db");
    
    return 0;
}
File Pointer Best Practices:
  • Always check if seek operations succeeded
  • Use ios::beg for absolute positioning
  • Use ios::cur for relative positioning
  • Use ios::end for operations from file end
  • Remember that binary and text modes affect positioning
  • Clear error flags before positioning operations

5. Error Handling in File Operations

Proper error handling is crucial for robust file operations. C++ provides several methods to detect and handle file operation errors.

Error Checking Methods
// 1. Using is_open()
ifstream file("data.txt");
if (!file.is_open()) {
    cerr << "Failed to open file!";
    return 1;
}

// 2. Using fail()
if (file.fail()) {
    cerr << "File operation failed!";
}

// 3. Using good()
while (file.good()) {
    // Safe to read
}

// 4. Using eof()
while (!file.eof()) {
    // Read until end of file
}
Stream State Methods
// Check stream state
if (file.rdstate() == ios::goodbit) {
    // All good
}

if (file.rdstate() & ios::failbit) {
    // Non-fatal error
}

if (file.rdstate() & ios::badbit) {
    // Fatal error
}

// Clear error state
file.clear();

// Set exception mask
file.exceptions(ios::failbit | ios::badbit);
Error Handling Examples
#include <iostream>
#include <fstream>
#include <string>
#include <system_error> // For error codes
#include <cerrno>       // For errno
#include <cstring>      // For strerror
using namespace std;

// Function to demonstrate different error scenarios
void demonstrateErrors() {
    cout << "=== File Error Handling Examples ===" << endl << endl;
    
    // 1. File not found error
    cout << "1. File Not Found Error:" << endl;
    ifstream noFile("non_existent_file.txt");
    
    if (!noFile.is_open()) {
        cout << "Error: Could not open non_existent_file.txt" << endl;
        cout << "Error code: " << errno << endl;
        cout << "Error message: " << strerror(errno) << endl;
    }
    noFile.close();
    cout << endl;
    
    // 2. Permission denied error
    cout << "2. Permission Denied Error:" << endl;
    // Try to open a system file (may require admin)
    ifstream systemFile("/etc/shadow"); // Linux system file
    
    if (!systemFile.is_open()) {
        cout << "Error: Permission denied for /etc/shadow" << endl;
        cout << "Error code: " << errno << endl;
        cout << "Error message: " << strerror(errno) << endl;
    } else {
        systemFile.close();
    }
    cout << endl;
    
    // 3. Disk full error simulation
    cout << "3. Disk Space Error (Simulated):" << endl;
    ofstream bigFile("huge_file.bin", ios::binary);
    
    if (bigFile.is_open()) {
        try {
            // Try to write more data than available
            const long long HUGE_SIZE = 1000000000000LL; // 1TB
            
            for (long long i = 0; i < HUGE_SIZE / sizeof(int); i++) {
                int data = i;
                bigFile.write((char*)&data, sizeof(data));
                
                // Check for errors periodically
                if (i % 1000000 == 0) {
                    if (bigFile.fail()) {
                        throw runtime_error("Write failed - possibly disk full");
                    }
                }
                
                // Break early for demonstration
                if (i > 1000000) break;
            }
        } catch (const exception& e) {
            cout << "Exception: " << e.what() << endl;
        }
        bigFile.close();
    }
    cout << endl;
    
    // 4. Read past end of file
    cout << "4. Read Past End of File:" << endl;
    {
        // Create a small file
        ofstream small("small.txt");
        small << "Hello World";
        small.close();
        
        ifstream readSmall("small.txt");
        if (readSmall.is_open()) {
            string content;
            
            // Read first line
            getline(readSmall, content);
            cout << "First read: " << content << endl;
            
            // Check state before second read
            cout << "Before second read:" << endl;
            cout << "good(): " << readSmall.good() << endl;
            cout << "eof(): " << readSmall.eof() << endl;
            cout << "fail(): " << readSmall.fail() << endl;
            cout << "bad(): " << readSmall.bad() << endl;
            
            // Try to read past EOF
            string shouldFail;
            getline(readSmall, shouldFail);
            
            cout << "\nAfter second read:" << endl;
            cout << "good(): " << readSmall.good() << endl;
            cout << "eof(): " << readSmall.eof() << endl;
            cout << "fail(): " << readSmall.fail() << endl;
            cout << "bad(): " << readSmall.bad() << endl;
            
            // Clear error state
            readSmall.clear();
            cout << "\nAfter clear():" << endl;
            cout << "good(): " << readSmall.good() << endl;
            
            readSmall.close();
        }
        remove("small.txt");
    }
    cout << endl;
    
    // 5. Using rdstate() for detailed error checking
    cout << "5. Using rdstate() for Error Checking:" << endl;
    {
        fstream testFile("test_state.txt", ios::in | ios::out | ios::trunc);
        
        if (testFile.is_open()) {
            // Write some data
            testFile << "Test data\n";
            
            // Check initial state
            ios::iostate state = testFile.rdstate();
            cout << "Initial state: " << state << endl;
            cout << "goodbit set: " << ((state & ios::goodbit) != 0) << endl;
            
            // Force an error by seeking past end
            testFile.seekg(1000, ios::beg);
            state = testFile.rdstate();
            cout << "\nAfter invalid seek:" << endl;
            cout << "State: " << state << endl;
            cout << "failbit set: " << ((state & ios::failbit) != 0) << endl;
            
            // Clear and restore
            testFile.clear();
            testFile.seekg(0, ios::beg);
            state = testFile.rdstate();
            cout << "\nAfter clear and valid seek:" << endl;
            cout << "State: " << state << endl;
            cout << "goodbit set: " << ((state & ios::goodbit) != 0) << endl;
            
            testFile.close();
        }
        remove("test_state.txt");
    }
    cout << endl;
    
    // 6. Using exceptions with file streams
    cout << "6. Exception Handling with Files:" << endl;
    {
        fstream excFile;
        
        // Enable exceptions
        excFile.exceptions(ios::failbit | ios::badbit);
        
        try {
            // This should throw an exception
            excFile.open("non_existent_exc.txt", ios::in);
            
            // If we get here, file opened successfully
            string line;
            getline(excFile, line);
            excFile.close();
            
        } catch (const ios_base::failure& e) {
            cout << "Caught ios_base::failure: " << e.what() << endl;
            cout << "Error code: " << e.code() << endl;
        } catch (const exception& e) {
            cout << "Caught exception: " << e.what() << endl;
        }
    }
    cout << endl;
    
    // 7. Robust file reading with error checking
    cout << "7. Robust File Reading Pattern:" << endl;
    {
        // Create test file
        ofstream createTest("robust_test.txt");
        createTest << "Line 1\nLine 2\nLine 3\n";
        createTest.close();
        
        ifstream robustFile("robust_test.txt");
        
        if (robustFile.is_open()) {
            string line;
            int lineCount = 0;
            
            // Robust reading pattern
            while (true) {
                // Try to read a line
                getline(robustFile, line);
                
                // Check what happened
                if (robustFile.eof()) {
                    cout << "Reached end of file after " << lineCount << " lines." << endl;
                    break;
                } else if (robustFile.fail()) {
                    cout << "Read failed at line " << (lineCount + 1) << endl;
                    robustFile.clear(); // Clear error state
                    break;
                } else if (robustFile.bad()) {
                    cout << "Critical error at line " << (lineCount + 1) << endl;
                    break;
                } else {
                    // Successful read
                    lineCount++;
                    cout << "Line " << lineCount << ": " << line << endl;
                }
            }
            
            robustFile.close();
        }
        remove("robust_test.txt");
    }
    cout << endl;
    
    // 8. File operation wrapper with error handling
    cout << "8. File Operation Wrapper Class:" << endl;
    
    class SafeFile {
    private:
        fstream file;
        string filename;
        
    public:
        SafeFile(const string& fname, ios::openmode mode = ios::in | ios::out)
            : filename(fname) {
            file.open(filename, mode);
            
            if (!file.is_open()) {
                string error = "Failed to open file: " + filename;
                error += "\nError: " + string(strerror(errno));
                throw runtime_error(error);
            }
        }
        
        ~SafeFile() {
            if (file.is_open()) {
                file.close();
            }
        }
        
        template<typename T>
        void write(const T& data) {
            if (!file.good()) {
                throw runtime_error("File stream not in good state for writing");
            }
            
            file.write((char*)&data, sizeof(T));
            
            if (file.fail()) {
                throw runtime_error("Write operation failed");
            }
        }
        
        template<typename T>
        T read() {
            if (!file.good()) {
                throw runtime_error("File stream not in good state for reading");
            }
            
            T data;
            file.read((char*)&data, sizeof(T));
            
            if (file.fail()) {
                throw runtime_error("Read operation failed");
            }
            
            return data;
        }
        
        void seekg(streampos pos, ios::seekdir dir = ios::beg) {
            file.seekg(pos, dir);
            if (file.fail()) {
                throw runtime_error("Seek operation failed");
            }
        }
        
        bool isOpen() const { return file.is_open(); }
    };
    
    // Test the wrapper
    try {
        SafeFile safe("safe_test.bin", ios::binary | ios::in | ios::out | ios::trunc);
        cout << "File opened successfully." << endl;
        
        // Write some data
        int testData = 42;
        safe.write(testData);
        cout << "Data written: " << testData << endl;
        
        // Read it back
        safe.seekg(0);
        int readData = safe.read<int>();
        cout << "Data read: " << readData << endl;
        
        // Verify
        if (testData == readData) {
            cout << "Data verification successful!" << endl;
        }
        
    } catch (const exception& e) {
        cout << "Error in SafeFile: " << e.what() << endl;
    }
    
    remove("safe_test.bin");
    remove("huge_file.bin");
}

int main() {
    demonstrateErrors();
    return 0;
}
Error Handling Best Practices:
  • Always check is_open() after opening a file
  • Use good() before read/write operations
  • Check eof() after reading to detect end of file
  • Use clear() to reset error states when appropriate
  • Consider using exceptions for critical errors
  • Log errors with descriptive messages and error codes
  • Implement retry logic for transient errors

6. Object Serialization

Serialization is the process of converting objects into a format that can be stored or transmitted. In C++, this often means writing object data to files.

Serialization Examples
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
#include <type_traits>
#include <cstring>
using namespace std;

// 1. Basic serialization for simple structures
struct Person {
    int id;
    char name[50];
    int age;
    double salary;
    
    // Serialization method
    void serialize(ofstream& file) const {
        file.write((char*)&id, sizeof(id));
        file.write(name, sizeof(name));
        file.write((char*)&age, sizeof(age));
        file.write((char*)&salary, sizeof(salary));
    }
    
    // Deserialization method
    void deserialize(ifstream& file) {
        file.read((char*)&id, sizeof(id));
        file.read(name, sizeof(name));
        file.read((char*)&age, sizeof(age));
        file.read((char*)&salary, sizeof(salary));
    }
    
    void display() const {
        cout << "ID: " << id 
             << ", Name: " << name 
             << ", Age: " << age 
             << ", Salary: $" << salary << endl;
    }
};

// 2. Serialization with dynamic strings
class Employee {
private:
    int employeeId;
    string name;
    string department;
    vector<string> projects;
    
public:
    Employee(int id = 0, string n = "", string dept = "") 
        : employeeId(id), name(n), department(dept) {}
    
    void addProject(const string& project) {
        projects.push_back(project);
    }
    
    // Serialize to binary file
    void serialize(ofstream& file) const {
        // Write fixed-size data
        file.write((char*)&employeeId, sizeof(employeeId));
        
        // Write string with length prefix
        size_t nameLen = name.size();
        file.write((char*)&nameLen, sizeof(nameLen));
        file.write(name.c_str(), nameLen);
        
        size_t deptLen = department.size();
        file.write((char*)&deptLen, sizeof(deptLen));
        file.write(department.c_str(), deptLen);
        
        // Write vector of projects
        size_t numProjects = projects.size();
        file.write((char*)&numProjects, sizeof(numProjects));
        
        for (const auto& project : projects) {
            size_t projLen = project.size();
            file.write((char*)&projLen, sizeof(projLen));
            file.write(project.c_str(), projLen);
        }
    }
    
    // Deserialize from binary file
    void deserialize(ifstream& file) {
        // Read fixed-size data
        file.read((char*)&employeeId, sizeof(employeeId));
        
        // Read string with length prefix
        size_t nameLen;
        file.read((char*)&nameLen, sizeof(nameLen));
        name.resize(nameLen);
        file.read(&name[0], nameLen);
        
        size_t deptLen;
        file.read((char*)&deptLen, sizeof(deptLen));
        department.resize(deptLen);
        file.read(&department[0], deptLen);
        
        // Read vector of projects
        size_t numProjects;
        file.read((char*)&numProjects, sizeof(numProjects));
        projects.resize(numProjects);
        
        for (size_t i = 0; i < numProjects; i++) {
            size_t projLen;
            file.read((char*)&projLen, sizeof(projLen));
            projects[i].resize(projLen);
            file.read(&projects[i][0], projLen);
        }
    }
    
    void display() const {
        cout << "\nEmployee ID: " << employeeId << endl;
        cout << "Name: " << name << endl;
        cout << "Department: " << department << endl;
        cout << "Projects: ";
        for (const auto& project : projects) {
            cout << project << " ";
        }
        cout << endl;
    }
};

// 3. Template-based serialization for simple types
template<typename T>
typename enable_if<is_trivially_copyable<T>::value>::type
serializeSimple(const T& obj, ofstream& file) {
    file.write((char*)&obj, sizeof(T));
}

template<typename T>
typename enable_if<is_trivially_copyable<T>::value>::type
deserializeSimple(T& obj, ifstream& file) {
    file.read((char*)&obj, sizeof(T));
}

// 4. Complex object with inheritance
class Shape {
protected:
    string color;
    int id;
    
public:
    Shape(string c = "black", int i = 0) : color(c), id(i) {}
    virtual ~Shape() = default;
    
    virtual void serialize(ofstream& file) const {
        size_t colorLen = color.size();
        file.write((char*)&colorLen, sizeof(colorLen));
        file.write(color.c_str(), colorLen);
        file.write((char*)&id, sizeof(id));
    }
    
    virtual void deserialize(ifstream& file) {
        size_t colorLen;
        file.read((char*)&colorLen, sizeof(colorLen));
        color.resize(colorLen);
        file.read(&color[0], colorLen);
        file.read((char*)&id, sizeof(id));
    }
    
    virtual void draw() const {
        cout << "Drawing Shape ID " << id << " in " << color << " color." << endl;
    }
    
    virtual string getType() const { return "Shape"; }
};

class Circle : public Shape {
private:
    double radius;
    
public:
    Circle(string c = "black", int i = 0, double r = 1.0) 
        : Shape(c, i), radius(r) {}
    
    void serialize(ofstream& file) const override {
        Shape::serialize(file);
        file.write((char*)&radius, sizeof(radius));
    }
    
    void deserialize(ifstream& file) override {
        Shape::deserialize(file);
        file.read((char*)&radius, sizeof(radius));
    }
    
    void draw() const override {
        cout << "Drawing Circle ID " << id 
             << " with radius " << radius 
             << " in " << color << " color." << endl;
    }
    
    string getType() const override { return "Circle"; }
    
    double area() const {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width;
    double height;
    
public:
    Rectangle(string c = "black", int i = 0, double w = 1.0, double h = 1.0) 
        : Shape(c, i), width(w), height(h) {}
    
    void serialize(ofstream& file) const override {
        Shape::serialize(file);
        file.write((char*)&width, sizeof(width));
        file.write((char*)&height, sizeof(height));
    }
    
    void deserialize(ifstream& file) override {
        Shape::deserialize(file);
        file.read((char*)&width, sizeof(width));
        file.read((char*)&height, sizeof(height));
    }
    
    void draw() const override {
        cout << "Drawing Rectangle ID " << id 
             << " " << width << "x" << height 
             << " in " << color << " color." << endl;
    }
    
    string getType() const override { return "Rectangle"; }
    
    double area() const {
        return width * height;
    }
};

// 5. Serialization manager for polymorphic objects
class ShapeSerializer {
public:
    static void serialize(const vector<Shape*>& shapes, const string& filename) {
        ofstream file(filename, ios::binary);
        
        if (!file.is_open()) {
            throw runtime_error("Cannot open file for writing");
        }
        
        size_t count = shapes.size();
        file.write((char*)&count, sizeof(count));
        
        for (const auto shape : shapes) {
            // Write type information
            string type = shape->getType();
            size_t typeLen = type.size();
            file.write((char*)&typeLen, sizeof(typeLen));
            file.write(type.c_str(), typeLen);
            
            // Serialize the object
            shape->serialize(file);
        }
        
        file.close();
    }
    
    static vector<Shape*> deserialize(const string& filename) {
        ifstream file(filename, ios::binary);
        vector<Shape*> shapes;
        
        if (!file.is_open()) {
            throw runtime_error("Cannot open file for reading");
        }
        
        size_t count;
        file.read((char*)&count, sizeof(count));
        
        for (size_t i = 0; i < count; i++) {
            // Read type information
            size_t typeLen;
            file.read((char*)&typeLen, sizeof(typeLen));
            string type(typeLen, ' ');
            file.read(&type[0], typeLen);
            
            // Create appropriate object based on type
            Shape* shape = nullptr;
            
            if (type == "Circle") {
                shape = new Circle();
            } else if (type == "Rectangle") {
                shape = new Rectangle();
            } else if (type == "Shape") {
                shape = new Shape();
            } else {
                throw runtime_error("Unknown shape type: " + type);
            }
            
            // Deserialize the object
            shape->deserialize(file);
            shapes.push_back(shape);
        }
        
        file.close();
        return shapes;
    }
};

int main() {
    cout << "=== Object Serialization Examples ===" << endl << endl;
    
    // 1. Simple structure serialization
    cout << "1. Simple Structure Serialization:" << endl;
    {
        Person p1 = {1001, "John Doe", 30, 75000.50};
        Person p2 = {1002, "Jane Smith", 28, 82000.75};
        
        // Serialize
        ofstream personFile("persons.bin", ios::binary);
        if (personFile.is_open()) {
            p1.serialize(personFile);
            p2.serialize(personFile);
            personFile.close();
            cout << "Persons serialized to file." << endl;
        }
        
        // Deserialize
        ifstream readPersonFile("persons.bin", ios::binary);
        if (readPersonFile.is_open()) {
            Person p1_read, p2_read;
            p1_read.deserialize(readPersonFile);
            p2_read.deserialize(readPersonFile);
            
            cout << "Deserialized persons:" << endl;
            p1_read.display();
            p2_read.display();
            
            readPersonFile.close();
        }
        remove("persons.bin");
    }
    cout << endl;
    
    // 2. Class with dynamic strings serialization
    cout << "2. Class with Dynamic Strings:" << endl;
    {
        Employee emp1(2001, "Alice Johnson", "Engineering");
        emp1.addProject("Project Alpha");
        emp1.addProject("Project Beta");
        emp1.addProject("Project Gamma");
        
        Employee emp2(2002, "Bob Williams", "Marketing");
        emp2.addProject("Campaign 2024");
        
        // Serialize
        ofstream empFile("employees.bin", ios::binary);
        if (empFile.is_open()) {
            emp1.serialize(empFile);
            emp2.serialize(empFile);
            empFile.close();
            cout << "Employees serialized to file." << endl;
        }
        
        // Deserialize
        ifstream readEmpFile("employees.bin", ios::binary);
        if (readEmpFile.is_open()) {
            Employee emp1_read, emp2_read;
            emp1_read.deserialize(readEmpFile);
            emp2_read.deserialize(readEmpFile);
            
            cout << "Deserialized employees:" << endl;
            emp1_read.display();
            emp2_read.display();
            
            readEmpFile.close();
        }
        remove("employees.bin");
    }
    cout << endl;
    
    // 3. Template serialization
    cout << "3. Template-based Serialization:" << endl;
    {
        ofstream templateFile("template.bin", ios::binary);
        if (templateFile.is_open()) {
            int intValue = 42;
            double doubleValue = 3.14159;
            char charValue = 'X';
            
            serializeSimple(intValue, templateFile);
            serializeSimple(doubleValue, templateFile);
            serializeSimple(charValue, templateFile);
            
            templateFile.close();
            cout << "Template values serialized." << endl;
        }
        
        ifstream readTemplate("template.bin", ios::binary);
        if (readTemplate.is_open()) {
            int readInt;
            double readDouble;
            char readChar;
            
            deserializeSimple(readInt, readTemplate);
            deserializeSimple(readDouble, readTemplate);
            deserializeSimple(readChar, readTemplate);
            
            cout << "Deserialized values:" << endl;
            cout << "int: " << readInt << endl;
            cout << "double: " << readDouble << endl;
            cout << "char: " << readChar << endl;
            
            readTemplate.close();
        }
        remove("template.bin");
    }
    cout << endl;
    
    // 4. Polymorphic object serialization
    cout << "4. Polymorphic Object Serialization:" << endl;
    {
        vector<Shape*> shapes;
        shapes.push_back(new Circle("red", 1, 5.0));
        shapes.push_back(new Rectangle("blue", 2, 4.0, 6.0));
        shapes.push_back(new Circle("green", 3, 3.0));
        shapes.push_back(new Rectangle("yellow", 4, 8.0, 12.0));
        
        // Display original shapes
        cout << "Original shapes:" << endl;
        for (const auto shape : shapes) {
            shape->draw();
        }
        
        // Serialize
        try {
            ShapeSerializer::serialize(shapes, "shapes.bin");
            cout << "\nShapes serialized to file." << endl;
        } catch (const exception& e) {
            cout << "Serialization error: " << e.what() << endl;
        }
        
        // Clean up original objects
        for (auto shape : shapes) {
            delete shape;
        }
        shapes.clear();
        
        // Deserialize
        try {
            shapes = ShapeSerializer::deserialize("shapes.bin");
            cout << "\nDeserialized shapes:" << endl;
            for (const auto shape : shapes) {
                shape->draw();
            }
        } catch (const exception& e) {
            cout << "Deserialization error: " << e.what() << endl;
        }
        
        // Clean up
        for (auto shape : shapes) {
            delete shape;
        }
        remove("shapes.bin");
    }
    cout << endl;
    
    // 5. Text-based serialization (JSON-like)
    cout << "5. Text-based Serialization (JSON-like):" << endl;
    {
        class Product {
        private:
            int id;
            string name;
            double price;
            vector<string> categories;
            
        public:
            Product(int i = 0, string n = "", double p = 0.0) 
                : id(i), name(n), price(p) {}
            
            void addCategory(const string& category) {
                categories.push_back(category);
            }
            
            string toJson() const {
                ostringstream json;
                json << "{\n";
                json << "  \"id\": " << id << ",\n";
                json << "  \"name\": \"" << name << "\",\n";
                json << "  \"price\": " << price << ",\n";
                json << "  \"categories\": [";
                
                for (size_t i = 0; i < categories.size(); i++) {
                    json << "\"" << categories[i] << "\"";
                    if (i < categories.size() - 1) {
                        json << ", ";
                    }
                }
                json << "]\n";
                json << "}";
                
                return json.str();
            }
            
            void fromJson(const string& jsonStr) {
                // Simple JSON parsing (for demonstration)
                // In real applications, use a proper JSON library
                istringstream iss(jsonStr);
                string line;
                
                while (getline(iss, line)) {
                    // Remove whitespace
                    line.erase(0, line.find_first_not_of(" \t"));
                    line.erase(line.find_last_not_of(" \t") + 1);
                    
                    if (line.find("\"id\":") != string::npos) {
                        size_t pos = line.find(":");
                        string value = line.substr(pos + 1);
                        value.erase(0, value.find_first_not_of(" \t"));
                        value.erase(value.find_last_not_of(" \t,") + 1);
                        id = stoi(value);
                    } else if (line.find("\"name\":") != string::npos) {
                        size_t start = line.find("\"", line.find(":")) + 1;
                        size_t end = line.find("\"", start);
                        name = line.substr(start, end - start);
                    } else if (line.find("\"price\":") != string::npos) {
                        size_t pos = line.find(":");
                        string value = line.substr(pos + 1);
                        value.erase(0, value.find_first_not_of(" \t"));
                        value.erase(value.find_last_not_of(" \t,") + 1);
                        price = stod(value);
                    }
                }
            }
            
            void display() const {
                cout << "Product ID: " << id << endl;
                cout << "Name: " << name << endl;
                cout << "Price: $" << price << endl;
                cout << "Categories: ";
                for (const auto& cat : categories) {
                    cout << cat << " ";
                }
                cout << endl;
            }
        };
        
        Product prod1(101, "Laptop", 999.99);
        prod1.addCategory("Electronics");
        prod1.addCategory("Computers");
        
        Product prod2(102, "Desk Chair", 199.50);
        prod2.addCategory("Furniture");
        prod2.addCategory("Office");
        
        // Serialize to text file
        ofstream jsonFile("products.json");
        if (jsonFile.is_open()) {
            jsonFile << prod1.toJson() << "\n\n";
            jsonFile << prod2.toJson();
            jsonFile.close();
            cout << "Products serialized to JSON file." << endl;
        }
        
        // Read and parse
        ifstream readJson("products.json");
        if (readJson.is_open()) {
            string line, jsonText;
            while (getline(readJson, line)) {
                jsonText += line + "\n";
            }
            readJson.close();
            
            cout << "\nJSON content:" << endl;
            cout << jsonText << endl;
        }
        
        remove("products.json");
    }
    
    return 0;
}
Serialization Considerations:
  • Binary serialization is not portable across different architectures
  • Always include version information in serialized data
  • Handle pointer and reference members carefully
  • Consider using established formats (JSON, XML, Protocol Buffers)
  • Validate deserialized data to prevent security issues
  • Implement backward compatibility for data format changes

7. Best Practices and Common Mistakes

Best Practices
  • Always check if file opened successfully
  • Close files in reverse order of opening
  • Use RAII principles for file handling
  • Specify full paths or handle relative paths carefully
  • Use appropriate file modes for intended operations
  • Implement proper error handling and logging
  • Validate data read from files
Common Mistakes
  • Not closing files (resource leaks)
  • Assuming file operations always succeed
  • Using text mode for binary data
  • Not checking for EOF correctly
  • Buffer overflows when reading strings
  • Not handling different line endings
  • Race conditions in multi-threaded file access
Good vs Bad File Handling
#include <iostream>
#include <fstream>
#include <string>
#include <memory>
using namespace std;

// BAD PRACTICES
void badFileHandling() {
    cout << "=== BAD FILE HANDLING PRACTICES ===" << endl;
    
    // 1. Not checking if file opened
    ofstream file1("bad1.txt");
    file1 << "This might not work!"; // File might not be open!
    // file1 not closed explicitly
    
    // 2. Using raw pointers without cleanup
    ifstream* file2 = new ifstream("bad2.txt");
    // ... use file2 ...
    // delete file2; // MEMORY LEAK!
    
    // 3. Incorrect EOF checking
    ifstream file3("bad3.txt");
    while (!file3.eof()) { // WRONG: eof() only true after failed read
        string line;
        file3 >> line;     // Might fail at EOF
        cout << line;      // Might print last line twice
    }
    
    // 4. Not specifying binary mode for binary data
    ofstream file4("bad4.bin"); // Should be ios::binary
    int data = 42;
    file4 << data; // Text representation, not binary!
    
    // 5. Buffer overflow risk
    char buffer[10];
    ifstream file5("bad5.txt");
    file5 >> buffer; // Input longer than 9 chars will overflow!
    
    cout << "Bad practices demonstrated (errors may occur)." << endl << endl;
}

// GOOD PRACTICES
void goodFileHandling() {
    cout << "=== GOOD FILE HANDLING PRACTICES ===" << endl;
    
    // 1. Always check file open status
    ofstream file1("good1.txt");
    if (!file1.is_open()) {
        cerr << "Error: Could not open good1.txt" << endl;
        return;
    }
    file1 << "This will work correctly.";
    file1.close(); // Explicit close
    
    // 2. Use RAII (Resource Acquisition Is Initialization)
    {
        ifstream file2("good2.txt");
        if (file2.is_open()) {
            // File will be closed automatically when out of scope
            string line;
            while (getline(file2, line)) {
                cout << line << endl;
            }
        }
    } // file2 automatically closed here
    
    // 3. Smart pointers for dynamic allocation
    auto file3 = make_unique<ifstream>("good3.txt");
    if (file3->is_open()) {
        // No need to delete, unique_ptr handles it
        string content;
        *file3 >> content;
    }
    
    // 4. Correct EOF checking
    ifstream file4("good4.txt");
    if (file4.is_open()) {
        string line;
        while (getline(file4, line)) { // Correct: getline returns stream
            cout << line << endl;
        }
    }
    
    // 5. Safe binary operations
    ofstream file5("good5.bin", ios::binary);
    if (file5.is_open()) {
        int data = 42;
        file5.write((char*)&data, sizeof(data));
        file5.close();
    }
    
    // 6. Safe string reading
    ifstream file6("good6.txt");
    if (file6.is_open()) {
        string safeBuffer;
        file6 >> safeBuffer; // No overflow with std::string
        cout << "Read: " << safeBuffer << endl;
    }
    
    // 7. Using getline with delimiter
    ifstream csvFile("data.csv");
    if (csvFile.is_open()) {
        string line;
        while (getline(csvFile, line)) {
            stringstream ss(line);
            string token;
            while (getline(ss, token, ',')) {
                cout << token << "\t";
            }
            cout << endl;
        }
    }
    
    // 8. File wrapper class for RAII
    class SafeFile {
    private:
        fstream file;
        string filename;
        
    public:
        SafeFile(const string& fname, ios::openmode mode) 
            : filename(fname) {
            file.open(filename, mode);
            if (!file.is_open()) {
                throw runtime_error("Failed to open: " + filename);
            }
        }
        
        ~SafeFile() {
            if (file.is_open()) {
                file.close();
            }
        }
        
        // Delete copy constructor and assignment
        SafeFile(const SafeFile&) = delete;
        SafeFile& operator=(const SafeFile&) = delete;
        
        // Allow move operations
        SafeFile(SafeFile&& other) noexcept 
            : file(move(other.file)), filename(move(other.filename)) {}
        
        fstream& get() { return file; }
        
        bool isOpen() const { return file.is_open(); }
    };
    
    try {
        SafeFile safe("safe.txt", ios::out | ios::trunc);
        safe.get() << "Written by SafeFile" << endl;
        // Automatically closed when out of scope
    } catch (const exception& e) {
        cerr << "Error: " << e.what() << endl;
    }
    
    // 9. Proper error handling with exceptions
    ifstream exFile;
    exFile.exceptions(ios::failbit | ios::badbit);
    
    try {
        exFile.open("might_not_exist.txt");
        // If we get here, file is open
        string content;
        exFile >> content;
        exFile.close();
    } catch (const ios_base::failure& e) {
        cerr << "File operation failed: " << e.what() << endl;
        exFile.clear(); // Clear error state
    }
    
    // 10. Checking file existence (C++17)
    #if __cplusplus >= 201703L
    #include <filesystem>
    namespace fs = std::filesystem;
    
    string path = "some_file.txt";
    if (fs::exists(path)) {
        cout << path << " exists." << endl;
        cout << "File size: " << fs::file_size(path) << " bytes" << endl;
    } else {
        cout << path << " does not exist." << endl;
    }
    #endif
    
    cout << "Good practices demonstrated." << endl;
}

// Utility functions for file operations
namespace FileUtils {
    bool writeTextFile(const string& filename, const string& content) {
        ofstream file(filename);
        if (!file.is_open()) return false;
        
        file << content;
        return !file.fail();
    }
    
    string readTextFile(const string& filename) {
        ifstream file(filename);
        if (!file.is_open()) return "";
        
        string content;
        string line;
        while (getline(file, line)) {
            content += line + "\n";
        }
        
        return content;
    }
    
    bool copyFile(const string& source, const string& destination) {
        ifstream src(source, ios::binary);
        ofstream dst(destination, ios::binary);
        
        if (!src.is_open() || !dst.is_open()) return false;
        
        dst << src.rdbuf();
        return !src.fail() && !dst.fail();
    }
    
    bool appendToFile(const string& filename, const string& content) {
        ofstream file(filename, ios::app);
        if (!file.is_open()) return false;
        
        file << content;
        return !file.fail();
    }
}

int main() {
    badFileHandling();
    cout << endl;
    goodFileHandling();
    
    // Test utility functions
    cout << "\n=== Testing Utility Functions ===" << endl;
    
    if (FileUtils::writeTextFile("test_util.txt", "Hello, World!\nThis is a test.")) {
        cout << "File written successfully." << endl;
        
        string content = FileUtils::readTextFile("test_util.txt");
        cout << "File content:\n" << content << endl;
        
        if (FileUtils::appendToFile("test_util.txt", "\nAppended text.")) {
            cout << "Text appended." << endl;
        }
        
        if (FileUtils::copyFile("test_util.txt", "test_util_copy.txt")) {
            cout << "File copied." << endl;
        }
    }
    
    // Cleanup
    remove("test_util.txt");
    remove("test_util_copy.txt");
    remove("good1.txt");
    remove("good5.bin");
    
    return 0;
}