Mastering C++ Constructors and Destructors

In C++, the lifecycles of objects are managed automatically or dynamically through two special types of member functions: Constructors and Destructors. They control how memory is allocated, initialized, and cleaned up when an object is created and destroyed.

1. What is a Constructor?

A constructor is a special member function that is automatically called when an object of a class is instantiated. It has the same name as the class and does not have a return type (not even void). Its primary purpose is to initialize the object’s data members.

Instantiation of Objects

Instantiation is the process of creating a physical instance (an object) of a class template in memory. When you instantiate an object, the compiler allocates memory for it and invokes the appropriate constructor to set up its initial state.

class Line {
public:
    int length;
    // Constructor
    Line() {
        length = 0;
    }
};

int main() {
    Line myLine; // Instantiation (triggers the constructor)
}

2. Types of Constructors

C++ supports three main types of constructors based on how variables need to be set up during initialization.

A. Default Constructor

A constructor that accepts no arguments. If you do not explicitly define any constructor in your class, the compiler automatically generates an empty default constructor for you.

class Point {
    int x, y;
public:
    Point() { // Default Constructor
        x = 0;
        y = 0;
    }
};

B. Parameterized Constructor

A constructor that accepts arguments. This allows you to pass distinct initial values to different objects at the moment they are created.

class Point {
    int x, y;
public:
    Point(int x_val, int y_val) { // Parameterized Constructor
        x = x_val;
        y = y_val;
    }
};

// Instantiation:
Point p1(10, 20);

C. Copy Constructor

A copy constructor initializes a brand-new object using an existing object of the same class. It takes a reference to an object of the same class as a parameter.

  • Syntax: ClassName(const ClassName &source_object);
  • Why it passes by reference: If we passed it by value, it would try to make a copy of the argument, which would call the copy constructor again, leading to an infinite recursive loop until the stack overflows.
class Point {
    int x, y;
public:
    Point(int x_val, int y_val) { x = x_val; y = y_val; }

    // Copy Constructor
    Point(const Point &p) { 
        x = p.x;
        y = p.y;
        clog << "Copy constructor called!" << endl;
    }
};

// Usage:
Point p1(10, 20);
Point p2 = p1; // Triggers Copy Constructor
Point p3(p1);  // Also triggers Copy Constructor

Key Uses of a Copy Constructor

  1. When initializing an object explicitly from another object of the same class.
  2. When an object is passed as an argument to a function by value.
  3. When a function returns an object by value.

3. Dynamic Initialization of Objects

Dynamic initialization means providing the initial values for an object at runtime rather than compile-time. This is highly useful when the initial values depend on user input or a file read operation, and it often involves allocating memory dynamically on the heap using the new operator.

#include <iostream>
using namespace std;

class BankAccount {
    int id;
    double balance;
public:
    BankAccount(int account_id, double initial_funds) {
        id = account_id;
        balance = initial_funds;
    }
};

int main() {
    int user_id;
    double user_deposit;

    // Values provided at runtime
    cout << "Enter Account ID and Deposit: ";
    cin >> user_id >> user_deposit;

    // Dynamic Initialization on the heap
    BankAccount *account_ptr = new BankAccount(user_id, user_deposit);

    delete account_ptr; // Clean up memory
    return 0;
}

4. Destructors

A destructor is a special member function that is automatically called when an object goes out of scope or is explicitly deleted via memory management. It cleans up resources (like closing open files, releasing network connections, or deallocating heap memory created via new) to prevent memory leaks.

Characteristics of Destructors

  • It has the exact same name as the class, prefixed with a tilde (~).
  • It does not accept any arguments (and therefore cannot be overloaded).
  • It has no return type.
#include <iostream>
using namespace std;

class Sample {
private:
    int *buffer;
public:
    Sample() {
        buffer = new int[100]; // Allocating resources
        cout << "Constructor: Resource allocated." << endl;
    }

    ~Sample() {
        delete[] buffer; // Releasing resources
        cout << "Destructor: Resource freed safely." << endl;
    }
};

void createObject() {
    Sample s; // Constructor runs here
} // Destructor runs automatically here as 's' goes out of scope

int main() {
    createObject();
    return 0;
}