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
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
#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;
}
- 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 (|)
- 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;
}
#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));
#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
#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;
}
- Always check if seek operations succeeded
- Use
ios::begfor absolute positioning - Use
ios::curfor relative positioning - Use
ios::endfor 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);
#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;
}
- 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.
#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;
}
- 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
#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;
}