Unit 3: Data Files, Constructors and Destructors

CSE202 — Object Oriented Programming 6 min read

I. Orientation: Persistent Data and Object Lifetime

C++ file handling stores data outside program memory, while constructors and destructors control an object's valid lifetime. File streams connect a program to external data; manager functions establish and release the resources owned by objects.

  • Persistence: Variables normally disappear when a program ends, but data written to a file remains available for later execution.
  • Stream model: A stream is a flow of bytes between a program and a source or destination. C++ provides file streams through the <fstream> header.
  • File positions: Streams maintain a get pointer for reading and a put pointer for writing.
  • Text and binary formats: Text files store formatted characters; binary files store byte representations without textual conversion.
  • Object lifetime: An object's lifetime begins after construction succeeds and ends when its destructor finishes.
  • RAII convention: Resource Acquisition Is Initialization ties resources such as files or memory to object lifetime, enabling automatic cleanup.
  • Manager functions: Constructors initialize objects, while destructors release resources and perform final cleanup.

II. File Stream Fundamentals — Connecting Programs to Files

File stream objects provide controlled communication between a C++ program and files stored on secondary storage.

A. Opening and closing files

Opening associates a stream object with a named file, and closing ends that association.

  • Stream classes:
    • ifstream: Reads from files.
    • ofstream: Writes to files.
    • fstream: Supports both reading and writing.
  • Constructor opening: A filename and optional mode can be supplied when the stream is created.
  • Explicit opening: The open() member allows a previously created stream to be connected later.
  • Status check: is_open() confirms association, while if (!file) detects an opening or stream failure.
  • Closing: close() flushes buffered output and releases the operating-system file handle. Destruction also closes an open stream automatically.
CPP
#include <fstream>
using namespace std;

ifstream input("marks.txt");
if (!input.is_open()) {
    return 1;
}
input.close();

B. File modes

File modes specify the permitted operation and the treatment of existing content.

  • ios::in: Opens a file for input.
  • ios::out: Opens for output; with ofstream, existing content is normally truncated unless combined with modes such as app.
  • ios::app: Forces every write to the end of the file.
  • ios::ate: Opens the file and initially places the position at the end, but later seeking is permitted.
  • ios::trunc: Discards existing contents when the file opens.
  • ios::binary: Prevents text-mode translations and processes raw bytes.
  • Combined modes: The bitwise OR operator joins compatible flags.
CPP
fstream file("records.dat",
             ios::in | ios::out | ios::binary);

C. File stream functions

File stream functions manage data transfer, positions, and stream state.

  • Character operations: get(ch) reads one character; put(ch) writes one character.
  • Line operation: getline(stream, text) reads through a delimiter, normally newline, and removes that delimiter.
  • Binary operations: read() and write() transfer a specified number of bytes.
  • Position operations: seekg() and tellg() control or report the get position; seekp() and tellp() handle the put position.
  • State operations:
    • good(): No error flags are set.
    • eof(): End-of-file has been encountered by an attempted read.
    • fail(): A formatting or operational failure occurred.
    • bad(): A serious input/output error occurred.
    • clear(): Resets state flags, often before seeking after EOF.

D. Reading and writing files

Formatted file operations use extraction and insertion operators similarly to console input and output.

  • Writing: << converts values such as 42 or 3.5 into character sequences.
  • Reading: >> converts file characters into a value of the requested type and normally skips leading whitespace.
  • Whole lines: getline() preserves spaces within text such as "Ada Lovelace".
  • Correct loop: The read operation itself should control iteration; while (!file.eof()) can process stale data after a failed read.
  • Concrete example:
CPP
ofstream out("marks.txt");
out << 101 << ' ' << 86.5 << '\n';
out.close();

ifstream in("marks.txt");
int roll;
double mark;
while (in >> roll >> mark) {
    // Use one successfully read record.
}

III. File Processing Methods — Locating and Representing Records

File-processing strategy determines whether records are visited in order or reached directly through calculated positions.

A. Sequential access and random access file processing

Sequential access processes records in storage order, whereas random access moves directly to a selected byte position.

  1. Sequential access:
    • Principle: Each record is read after the preceding record, usually with while (file >> value).
    • Strength: It suits logs, reports, and variable-length text records.
    • Limitation: Reaching record 1,000 generally requires processing records 1 through 999 first.
  2. Random access:
    • Principle: seekg() or seekp() repositions a stream relative to ios::beg, ios::cur, or ios::end.
    • Fixed-record calculation: For zero-based record number n and record size S, the byte offset is n * S.
    • Strength: A fixed-size record can be retrieved or updated without scanning the entire file.
CPP
streamoff offset = static_cast<streamoff>(n) * sizeof(Record);
file.seekg(offset, ios::beg);

Here, n is the zero-based record index, Record is the stored fixed-size type, and offset is the resulting byte displacement.

B. Binary file operations

Binary operations transfer bytes directly, avoiding formatted text conversion.

  • Output operation: write(address, count) copies count bytes from memory to the file.
  • Input operation: read(address, count) copies bytes from the file into memory.
  • Required conversion: The memory address is supplied as char* or const char*, commonly through reinterpret_cast.
  • Advantages: Numeric values usually occupy fixed space and can be read without parsing decimal characters.
  • Restrictions: Raw binary files can depend on type size, byte order, padding, and compiler representation, reducing portability.
  • Suitable types: Direct object-byte storage should be limited to trivially copyable data, not objects containing pointers, std::string, virtual functions, or owning containers.
CPP
Record r{101, 86.5};
ofstream out("record.dat", ios::binary);
out.write(reinterpret_cast<const char*>(&r), sizeof(r));

IV. User-Defined Records — Integrating Data Models with Streams

Classes and structures can represent complete records, but their file format should match their internal data and portability requirements.

A. Classes and file operations

A class can encapsulate both record data and the operations used to serialize it.

  • Text serialization: A member function may write each field with delimiters, allowing values such as an identifier and name to be reconstructed.
  • Encapsulation: Private fields remain protected because public functions or overloaded stream operators control file access.
  • Operator support: operator<< and operator>> can provide a consistent stream interface.
  • Resource-owning class: A class containing an fstream can open it in a constructor and rely on its destructor for automatic closure.
  • Safety rule: A class containing std::string must serialize the characters or length explicitly; dumping the object's bytes stores implementation details rather than valid string content.
CPP
class Student {
    int roll{};
    string name;
public:
    void save(ostream& out) const {
        out << roll << '\n' << name << '\n';
    }
};

B. Structures and file operations

A structure groups related fields and is commonly used for simple fixed-format records.

  • Default accessibility: Structure members are public unless another access specifier is declared.
  • Text processing: Individual members can be written using << and read using >> or getline().
  • Binary processing: A structure containing only suitable fixed-size values can be transferred with read() and write().
  • Fixed-size example: struct Record { int id; double score; }; supports offset calculations using sizeof(Record).
  • Layout caution: Padding may occur between members, so raw structure files are not guaranteed to be portable across systems or compilers.

V. Object Lifetime Management — Establishing and Releasing State

Constructors create valid object state, and destructors perform cleanup when that state reaches the end of its lifetime.

A. Manager functions: constructors and destructor

Manager functions are special member functions that govern object initialization, copying, and destruction.

  • Constructor identity: A constructor has the same name as its class and has no return type, not even void.
  • Automatic call: Construction occurs when an object is created, such as Account a;.
  • Overloading: A class may provide several constructors with different parameter lists.
  • Destructor identity: A destructor is named ~ClassName() and has neither parameters nor a return type.
  • Resource role: Constructors may acquire memory or open files; destructors release memory or close associated resources.

B. Default constructor

A default constructor can be invoked without supplying arguments.

  • Form: ClassName(); is an explicitly declared default constructor.
  • Purpose: It establishes predictable initial values instead of leaving fundamental members indeterminate.
  • Compiler behavior: If no constructor is user-declared, the compiler may implicitly declare a default constructor.
  • Value initialization: Member initializers such as int count{}; set fundamental values to zero.
CPP
class Counter {
    int value;
public:
    Counter() : value(0) {}
};

C. Constructor with default arguments

A constructor with default arguments allows omitted trailing arguments to receive predefined values.

  • Declaration: Box(int h = 1, int w = 1); can be called as Box(), Box(5), or Box(5, 8).
  • Placement rule: After the first defaulted parameter, all following parameters must also have defaults.
  • Ambiguity risk: Defining both Box() and Box(int h = 1) makes Box b; ambiguous because either constructor matches.
  • Concrete values: Box(5) assigns h = 5 and uses the default w = 1.

D. Destructors

A destructor performs final actions immediately before an object's storage is released.

  • Invocation: Automatic objects are destroyed when their scope ends; dynamically allocated objects are destroyed by delete.
  • Order: Local objects are destroyed in reverse order of completed construction.
  • Member destruction: After the destructor body runs, data members and base-class subobjects are destroyed automatically.
  • Uniqueness: A class has only one destructor because destructors accept no arguments and cannot be overloaded.
  • RAII application: Standard members such as fstream, string, and vector clean up their own resources, reducing manual destructor code.
  • Polymorphism: A base class deleted through a base pointer should generally have a virtual destructor.

E. Parameterized constructor

A parameterized constructor initializes an object from caller-supplied values.

  • Purpose: It ensures required information is available at creation, such as Account("A17", 500.0).
  • Validation: The constructor can reject invalid state, for example a negative opening balance.
  • Overloading: Different parameter lists can support different valid creation paths.
  • Direct construction: Point p(3, 4); passes 3 and 4 to Point(int, int).
CPP
class Point {
    int x, y;
public:
    Point(int xValue, int yValue)
        : x(xValue), y(yValue) {}
};

F. Copy constructor

A copy constructor creates a new object from an existing object of the same class.

  • Usual signature: ClassName(const ClassName& other) uses a const reference to avoid recursively copying the parameter.
  • Invocation: It is used in declarations such as Buffer b = a; and may be used in pass-by-value or return operations.
  • Compiler-generated copy: The implicit copy constructor performs member-by-member copying.
  • Shallow-copy danger: Copying an owning pointer duplicates only its address, potentially causing shared mutation or double deletion.
  • Deep copy: A resource-owning class may allocate separate storage and copy the pointed-to elements.
  • Modern preference: Standard containers and smart pointers express ownership more safely than manually managed arrays.

G. Initializer lists

A constructor initializer list initializes base classes and members before the constructor body executes.

  • Syntax: Initializers follow a colon, as in Point(int a) : x(a) {}.
  • Requirement: References, const members, and base classes must be initialized rather than assigned later.
  • Efficiency: Members are constructed directly with final values instead of being default-constructed and then assigned.
  • Actual order: Members initialize in their declaration order inside the class, not the written order in the initializer list.
  • Best practice: Write initializers in declaration order to make dependencies clear and avoid warnings.
CPP
class FileRecord {
    const int id;
    fstream file;
public:
    FileRecord(int value, const string& path)
        : id(value), file(path, ios::in | ios::out) {}
};