Unit 3: Constructors, Destructors and Managing File Operations

CAP455 — Object Oriented Programming Using C++ 6 min read

I. Orientation

Object-oriented C++ manages object lifetime through constructors and destructors, while file streams extend a program’s storage beyond temporary memory. A constructor establishes an object’s initial state when it is created; a destructor releases resources when the object’s lifetime ends. File operations use stream objects to transfer character or binary data between a program and an external file.

  • Object lifetime: Begins when an object is created and ends when it leaves scope or is explicitly destroyed.
  • Resource ownership: A class that acquires a file, memory block, or device resource should release it reliably.
  • Encapsulation: Data and the functions operating on it are grouped inside classes or structures.
  • Stream model: Input reads data into a program; output writes data from a program.
  • Text versus binary data: Text files store readable character representations, while binary files store bytes in their internal representation.
  • Access discipline: Files should be opened with an appropriate mode, checked for failure, processed, and closed.

II. Constructors

Constructors are special member functions that establish valid initial values for objects. They are called automatically during object creation and may be overloaded to support different initialization requirements.

A. Features of constructor function

A constructor has a special syntax and a specific role in object initialization.

  • Name: Its name is exactly the class name, such as Student::Student().
  • No return type: A constructor declares neither void nor any other return type.
  • Automatic invocation: Student s; calls a suitable constructor without an explicit function call.
  • Overloading: A class may define multiple constructors with different parameter lists.
  • Initialization purpose: Constructors should establish invariants, such as ensuring marks starts between 0 and 100.
  • No inheritance of constructors in the traditional sense: A derived object invokes a base constructor, but it has its own construction process.

B. Default constructor

A default constructor can be called without arguments and creates an object with default state.

  • User-defined form:
    CPP
      class Counter {
          int value;
      public:
          Counter() : value(0) {}
      };

    Counter c; initializes value to 0.
  • Compiler-generated form: If no constructor is declared, C++ may generate a default constructor, but built-in data members such as int value; are not automatically initialized to a useful value.
  • Explicit defaulting: Counter() = default; requests the compiler-generated behavior.
  • Use: Default construction is required by containers and algorithms when objects must be created before later assignment.

C. Constructor vs normal function

A constructor differs from an ordinary member function in purpose, invocation, and return behavior.

  • Invocation: A constructor runs automatically at object creation; display() must be called explicitly.
  • Return value: A constructor has no return type and cannot return a value; a normal function may return int, double, or another type.
  • Object identity: A constructor initializes the object whose creation triggered it; a normal function operates on an already existing object.
  • Call restrictions: Constructors cannot be called like ordinary functions using a return expression, although explicit temporary construction such as Point(2, 3) is valid.

D. Parameterized constructor

A parameterized constructor accepts values needed to initialize an object with caller-supplied state.

  • Definition:
    CPP
      class Rectangle {
          int width, height;
      public:
          Rectangle(int w, int h) : width(w), height(h) {}
          int area() const { return width * height; }
      };
  • Construction: Rectangle r(4, 5); creates an object whose area is 20.
  • Validation: Constructor bodies can reject or normalize invalid values, although exceptions are generally preferable for construction failure.
  • Effect on default construction: Declaring only Rectangle(int, int) means Rectangle r; is invalid unless a default constructor is also supplied.

E. Copy constructor

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

  • Canonical signature:
    CPP
      ClassName(const ClassName& other);

    The reference avoids copying again, and const permits copying from const objects.
  • Implicit uses: Account b = a;, passing an object by value, and returning an object by value may invoke it.
  • Shallow-copy risk: A raw pointer member copies only an address, causing two objects to refer to the same resource.
  • Deep copy: A resource-owning class should duplicate the resource rather than merely duplicate its pointer.
  • Rule of three: If a class defines a destructor, copy constructor, or copy-assignment operator for resource management, it often needs all three.

F. Initializer lists

An initializer list constructs data members before the constructor body executes.

  • Syntax:
    CPP
      class Employee {
          const int id;
          std::string name;
      public:
          Employee(int i, std::string n) : id(i), name(std::move(n)) {}
      };
  • Mandatory cases: const members, reference members, and members without default constructors must be initialized in the list.
  • Efficiency: name(std::move(n)) constructs the member directly, avoiding a default construction followed by assignment.
  • Order rule: Members initialize in their declaration order, not the order written in the list. Therefore, declare dependent members carefully.

G. Constructor with default arguments

Default arguments allow one constructor definition to support multiple call forms.

  • Example:
    CPP
      class Date {
          int day, month, year;
      public:
          Date(int d, int m = 1, int y = 2025)
              : day(d), month(m), year(y) {}
      };

    Date a(10); means day 10, month 1, year 2025.
  • Right-to-left rule: Once a parameter has a default value, parameters to its right must also have defaults.
  • Ambiguity: Combining a default-argument constructor with another overload may make calls such as Date(10) ambiguous.
  • Declaration practice: Put default arguments in the function declaration, usually in the class definition, not redundantly in the definition.

III. Destructors

A destructor performs cleanup when an object’s lifetime ends. Its purpose is resource release, not ordinary data processing.

A. Destructor

A destructor is named with the class name preceded by ~ and takes no parameters.

  • Syntax:
    CPP
      class Buffer {
          int* data;
      public:
          Buffer() : data(new int[10]) {}
          ~Buffer() { delete[] data; }
      };
  • Automatic call: A local object is destroyed at the closing brace of its scope; dynamically allocated objects require delete.
  • Reverse order: Local objects are destroyed in reverse order of construction, supporting dependency cleanup.
  • Single destructor: A class cannot overload destructors because destruction requires no distinguishing arguments.
  • Virtual destruction: A polymorphic base class should generally have a virtual destructor so delete basePtr; correctly destroys a derived object.
  • RAII principle: Resource Acquisition Is Initialization ties ownership to object lifetime, making cleanup automatic and exception-safe.

IV. File Streams

C++ file processing is provided mainly by <fstream>, whose stream classes represent external files.

A. Opening and closing of files

Opening associates a stream object with a file; closing ends that association and flushes pending output.

  • Output:
    CPP
      #include <fstream>
      std::ofstream out("report.txt");
      if (!out) { /* opening failed */ }
      out << "Total = " << 42 << '\n';
      out.close();
  • Input: std::ifstream in("report.txt"); opens a file for reading.
  • Combined access: std::fstream file("data.txt", std::ios::in | std::ios::out);.
  • Failure checking: is_open() checks association; fail() or operator! detects operational failure.
  • Automatic closing: A stream destructor closes its associated file, but explicit close() clearly marks the end of processing and allows another file to be opened.

B. Modes of file

File modes specify how a stream opens and positions a file.

  • ios::in: Opens for input; used by ifstream.
  • ios::out: Opens for output; an existing file may be truncated.
  • ios::app: Positions every write at the end, useful for logs.
  • ios::ate: Opens and initially positions at the end, but later seeking is allowed.
  • ios::trunc: Erases existing contents when opening for output.
  • ios::binary: Disables text translations and preserves byte-oriented data.
  • Combination: std::ofstream log("app.log", std::ios::app); appends records rather than replacing the log.

C. File stream functions

Stream functions control reading state, positioning, and error detection.

  • Character operations: get(ch) reads one character; put(ch) writes one character.
  • Line operations: getline(in, line) reads until a newline into a std::string.
  • State functions: eof() reports an end-of-file condition, while fail() reports a failed operation. Use while (std::getline(in, line)), not while (!in.eof()).
  • Position functions: tellg() and seekg() query or change the input position; tellp() and seekp() do the same for output.
  • Buffer control: flush() forces buffered output to the file.
  • Formatted operations: >> extracts typed values and << inserts formatted values.

V. File Processing Methods

The access method determines how records are located and updated.

A. Reading and writing of files

Reading transfers file content into variables; writing transfers formatted or raw program data into a stream.

  • Formatted writing:
    CPP
      std::ofstream out("marks.txt");
      out << "Asha " << 87 << '\n';

    stores a human-readable record.
  • Formatted reading: std::string name; int mark; in >> name >> mark; extracts whitespace-separated values.
  • Line-oriented reading: std::getline(in, line) preserves spaces within a line.
  • Validation: Check the stream after opening and after important operations because permissions, missing files, or disk errors can fail.
  • Format limitation: Formatted text is portable and readable, but it requires parsing and may lose exact object representation.

B. Sequential access and random access for file processing

Sequential access processes records in stored order, while random access jumps directly to a selected byte position.

  • Sequential access: A loop reads record 1, then record 2, and so on; it is suitable for reports and ordinary text files.
  • Random access: For fixed-size binary records, seekg(index * sizeof(Record), std::ios::beg) moves to record index.
  • Position units: seekg and seekp use byte offsets represented by stream positions.
  • Contrast:
    1. Sequential: Simple and effective for variable-length lines, but finding the last record may require scanning earlier records.
    2. Random: Efficient for fixed-size records, but record layout and byte offsets must remain compatible.
  • End positioning: seekg(0, std::ios::end) moves to the end; tellg() can then measure file size.

VI. Binary and Structured File Operations

Binary processing is useful when exact bytes, compact storage, or direct record access is required.

A. Binary file operations

Binary streams read and write blocks of bytes using read() and write().

  • Example:
    CPP
      struct Item { int id; double price; };
    
      Item x{7, 19.5};
      std::ofstream out("items.dat", std::ios::binary);
      out.write(reinterpret_cast<const char*>(&x), sizeof x);
  • Reading: in.read(reinterpret_cast<char*>(&x), sizeof x); fills x with the next sizeof x bytes.
  • Efficiency: One block operation can process a complete fixed-size record more efficiently than repeated formatted insertion.
  • Portability limitation: Padding, byte order, type sizes, and compiler representation can differ between systems.
  • Pointer warning: Never directly serialize a class containing a pointer and expect pointed-to data to be saved; the pointer value is only an address.

B. Classes and file operations

Classes can encapsulate file ownership and expose controlled persistence operations.

  • Encapsulation: A class may keep std::fstream file; private and provide save() and load() member functions.
  • Object serialization: Write each meaningful data member in a defined order rather than dumping an arbitrary object containing pointers or virtual-table data.
  • Resource safety: The class destructor closes the stream automatically, while constructor logic can open and validate it.
  • Invariant protection: load() should validate values such as an employee ID being positive before accepting them.
  • Copy concern: Stream objects are not generally copied freely; resource-owning file classes should define or disable copying deliberately.

C. Structures and file operations

Structures can represent simple records whose fields are stored and retrieved in a predictable order.

  • Text record: A Student structure with int roll; char name[30]; float marks; can be written as formatted fields for readability.
  • Binary record: A trivially copyable structure can sometimes be written with write(), provided portability and padding concerns are accepted.
  • Array processing: Student students[50]; can be processed sequentially, while fixed-size records permit indexed seeking.
  • Safe design: Prefer explicit field-by-field serialization when files must survive compiler, platform, or program-version changes.
  • Practical distinction: Structures mainly group data; classes can additionally enforce invariants, manage streams, and provide behavior through member functions.