01-classes-and-objects.md

Classes and Objects in C++: The Complete Guide

Table of Contents

  1. Procedural vs. Object-Oriented Programming (Why OOP?)
  2. Core Definitions: Classes and Objects
  3. Stack Allocation vs. Heap Allocation
  4. Struct vs. Class in C++
  5. In-Depth: Constructors
    • Default Constructor
    • Parameterized Constructor & Initializer Lists
    • Copy Constructor & Infinite Recursion
    • Move Constructor (noexcept and resource stealing)
  6. Destructors and Memory Management

1. Procedural vs. Object-Oriented Programming (Why OOP?)

Before OOP became the industry standard, most software was written using Procedural Programming (like in C). In procedural programming, you have data stored in global or local variables, and you write standalone functions to manipulate that data.

The Problem with Procedural:

  • It works great for small scripts.
  • It breaks down terribly in large systems. When hundreds of functions can modify the same data, tracking down bugs becomes a nightmare.

Why OOP? If an interviewer asks you "Why OOP?", the exact keywords they want to hear are:

  1. Scalability: It is much easier to scale a project when code is modularized into objects.
  2. Code Reusability: Through inheritance and composition, you don't have to rewrite the same logic.
  3. Maintainability: Because data and behavior are grouped together, fixing bugs in one module rarely breaks another module.

OOP (Object-Oriented Programming) is a programming paradigm—a specific style of writing code—that organizes software design around data, or "objects," or entities rather than standalone functions and logic.


2. Core Definitions: Classes and Objects

There are two fundamental concepts you must understand before writing any code:

1. The Class (The Blueprint)

A class is just a template or a blueprint. It does not exist in memory. It is just the design plan defining what data an entity will hold and what it can do.

  • Attributes (Data): What the object knows (variables like id, age, name).
  • Methods (Behavior): What the object does (functions like study(), sleep()).

2. The Object (Instance)

An object is the physical thing built from that blueprint. It actually lives in the computer's memory (RAM) and holds real, specific values. You can have millions of objects built from a single class blueprint.


3. Stack Allocation vs. Heap Allocation

When you create an object from a class, you have two choices for where it lives in memory: the Stack or the Heap.

Stack Allocation (Automatic Memory)

Student A(1, 1, 1, "John"); Student B = A; // Copying A into B
  • Where it lives: The Stack.
  • Speed: Extremely fast allocation.
  • Lifecycle: Automatic. The object is destroyed automatically when it goes out of scope (e.g., when the function ends). The Destructor is called automatically.

Heap Allocation (Dynamic Memory)

Student* A = new Student(1, 1, 1, "John"); delete A; // MUST BE DONE MANUALLY
  • Where it lives: The Heap (Free store).
  • Speed: Slower allocation.
  • Lifecycle: Manual. The object lives forever until you explicitly call delete.
  • Danger: If you forget to delete A;, you create a memory leak. The Destructor is NOT called automatically.

4. Struct vs Class in C++

In C++, struct and class are nearly identical. A struct in C++ can have functions, constructors, and destructors, just like a class. The ONLY difference is the default access modifier:

  • If you use class Child : Base, the default access mode is private. All members are private by default.
  • If you use struct Child : Base, the default access mode is public. All members are public by default.

5. In-Depth: Constructors

A constructor is a special function called at the exact millisecond an object is created.

  • The constructor name MUST be exactly the same as the class name.
  • It has NO return type (not even void).

The Complete Student Class Example

#include <iostream> #include <string> using namespace std; class Student { public: // Attributes int id; int age; int no_of_subjects; string name; // 1. Default Constructor // A constructor that accepts NO arguments. Student() { cout << "Default constructor called" << endl; } // 2. Parameterized Constructor // Accepts arguments, allowing you to initialize attributes with specific data. // Using 'this->' pointer which points to the current object. Student(int id, int age, int no_of_subjects, string name) { this->id = id; this->age = age; this->no_of_subjects = no_of_subjects; this->name = name; cout << "Parameterized constructor called" << endl; } // 3. Copy Constructor // Used to initialize a brand-new object using an already existing object. // MUST accept parameter as a 'const reference' (&) Student(const Student &source_obj) { this->id = source_obj.id; this->age = source_obj.age; this->no_of_subjects = source_obj.no_of_subjects; this->name = source_obj.name; cout << "Copy constructor called" << endl; } // Methods (Behavior) void study() { cout << this->name << " is Studying" << endl; } void sleep() { cout << this->name << " is Sleeping" << endl; } // Destructor ~Student() { cout << "Destructor called for " << this->name << endl; } };

The Infinite Recursion Trap (Copy Constructor)

Look closely at the Copy Constructor signature: Student(const Student &source_obj) Why must it be passed by reference (&)? If you passed it by value (Student source_obj), C++ needs to create a temporary copy of the object to pass it into the function. To create that copy, the compiler would have to call the copy constructor again. To pass the argument to that copy constructor, it calls it again. Result: An infinite recursion loop that results in a Stack Overflow compile-time error.

Member Initializer Lists (Performance Optimization)

Instead of assigning values inside the parameterized constructor body, modern C++ dictates using a Member Initializer List.

// Fast Initializer List Student(int _id, int _age) : id(_id), age(_age) {}

Why? Because it initializes the attributes directly when memory is allocated. If you do it inside the curly brackets, the compiler first allocates garbage data (or calls default constructors for complex types), and then overwrites it inside the brackets. Initializer lists skip the garbage allocation step.

4. The Move Constructor (Stealing Resources)

Introduced in C++11, the move constructor does not copy data from a temporary source object; instead, it steals (transfers ownership of) the heap resources directly.

Why we use it: Imagine a temporary, short-lived object holds a huge array of 1,000,000 integers on the heap. Instead of executing a slow deep copy to duplicate that massive array (only to immediately delete the temporary object), the move constructor just points the new object's pointer to the old heap address and sets the old object's pointer to nullptr.

class BigData { public: int* largeArray; BigData() { largeArray = new int[1000]; } // 🔥 THE MOVE CONSTRUCTOR // Takes an rvalue reference (&&) BigData(BigData&& source) noexcept { this->largeArray = source.largeArray; // Steal the address directly! source.largeArray = nullptr; // Strip ownership from the temporary source cout << "Move Constructor Triggered! Zero data was duplicated." << endl; } ~BigData() { // Safe because source.largeArray was set to nullptr during the move delete[] largeArray; } };

The noexcept Keyword: Notice the noexcept keyword? This guarantees to the compiler that stealing this memory will never throw an exception. Why it is mandatory for move constructors: Without noexcept, STL containers like std::vector and std::swap will completely bypass your optimized move logic and use slow deep copies instead to maintain strict exception safety guarantees. (Though technically not compulsory for compilation, it is practically mandatory for performance).


6. Destructors and Memory Management

  • Definition: Called when the function returns from where the object was created (goes out of scope), or when delete is called on a heap pointer.
  • Syntax: ~ClassName()
  • Rules: Cannot take arguments. Cannot be overloaded. You can only have ONE destructor per class.
  • Purpose: Used to close files, release network sockets, and delete dynamic memory to prevent leaks.