C++

Chapter 14: C++ Constructors and Destructors — The Object Lifecycle

By Ali Naqi • December 04, 2025

Chapter 14: C++ Constructors and Destructors — The Object Lifecycle

Chapter 14: Constructors and Destructors—The Object Lifecycle

In the previous chapters, we learned how to design a class and protect its data using encapsulation. However, the Student object we created was not fully functional until we manually called s1.setName("Alice"); and s1.setGrade(95);. What if we forget to call these setup methods? The object would be in an invalid, or partially initialized, state.

This chapter introduces two crucial, special member functions—the **Constructor** and the **Destructor**—which are automatically invoked by the C++ runtime to manage the entire lifecycle of an object, ensuring it is always created correctly and cleaned up safely.

Section 1: The Constructor (Ctor)—Object Initialization

A **Constructor** is a special method called automatically the moment an object is created (instantiated). Its sole purpose is to initialize the object's data members to a valid state.

Key Characteristics of Constructors:

  • **Name:** The constructor must have the exact same name as the class.
  • **Return Type:** Constructors have no return type, not even void.
  • **Invocation:** You never call a constructor explicitly; the C++ compiler calls it when you define a new object.

The Default Constructor

If you define a class but do not provide any constructors, the compiler generates a **Default Constructor** for you. This constructor takes no arguments and typically does nothing beyond default-initializing the members.


class Widget {
private:
    int id = 0; // Modern C++ provides in-class initialization
public:
    // User-defined Default Constructor (takes no arguments)
    Widget() {
        std::cout << "Widget created with default ID: " << id << std::endl;
        // A constructor is the best place to allocate default resources
    }
};

void demonstrate_default_ctor() {
    Widget w1; // Calls the default constructor: Widget()
    // Output: Widget created with default ID: 0
}

The Parameterized Constructor

The real power of constructors is in providing arguments, allowing the user to set the initial state of the object at the time of creation.


class Student {
private:
    std::string name;
    int grade;

public:
    // Parameterized Constructor
    Student(const std::string& n, int g) {
        // We can reuse the validation logic from the setter here!
        name = n;
        if (g >= 0 && g <= 100) {
            grade = g;
        } else {
            std::cerr << "Invalid grade supplied. Setting to 0." << std::endl;
            grade = 0;
        }
        std::cout << "Student " << name << " initialized." << std::endl;
    }
    
    // ... Getters and other methods go here
};

void demonstrate_parameterized_ctor() {
    // 1. Direct Initialization (Preferred Modern C++ style)
    Student s1("Bob", 85); 
    
    // 2. Uniform Initialization (using curly braces)
    Student s2{"Carol", 105}; 
    // s2's grade will be set to 0 due to validation
}

Constructor Overloading

A class can have multiple constructors, provided each has a unique signature (different number or types of parameters). This is called **Constructor Overloading**.


class Point {
public:
    int x, y;

    // Default Ctor
    Point() : x(0), y(0) {} // Initialization List (preferred)

    // Ctor with one argument
    Point(int val) : x(val), y(val) {}

    // Ctor with two arguments
    Point(int xVal, int yVal) : x(xVal), y(yVal) {}
};
Tip: Initialization Lists (: x(val))
The syntax : x(val), y(val) after the constructor signature is an **Initialization List**. It is the preferred, and often mandatory, way to initialize data members in C++. Unlike assignments (x = val;) which happen *inside* the constructor body, initialization lists set the values *before* the body executes, leading to better performance, especially for complex types. Always use initialization lists when possible.

Section 2: The Destructor (Dtor)—Safe Cleanup

The **Destructor** is a special method called automatically when an object is about to be destroyed (goes out of scope). Its primary purpose is to safely release any resources the object acquired during its lifetime, such as dynamic memory, file handles, or network connections.

Key Characteristics of Destructors:

  • **Name:** The destructor must have the same name as the class, prefixed with a tilde (~).
  • **Return Type:** No return type, not even void.
  • **Parameters:** Destructors take **no arguments** and therefore cannot be overloaded; a class can have only one destructor.

The Importance of Destructors (RAII)

When an object is destroyed, its local variables are cleaned up automatically. However, if the object allocated memory dynamically (using `new`), opened a file, or established a connection, C++ does *not* clean those resources automatically. If you fail to free them, you get a **resource leak**.

Destructors are the mechanism behind the C++ principle of **Resource Acquisition Is Initialization (RAII)**. This principle dictates that any resource must be acquired in the object's constructor (initialization) and released in the object's destructor (cleanup).


class FileHandler {
private:
    std::string filename;
    // Imagine this is a pointer to an open file stream
    void* fileResource = nullptr; 

public:
    // Constructor: Acquires the resource (opens the file)
    FileHandler(const std::string& name) : filename(name) {
        // Placeholder for file opening logic
        fileResource = new char[100]; 
        std::cout << "File '" << filename << "' opened and resource acquired." << std::endl;
    }

    // Destructor: Releases the resource (closes the file, frees memory)
    ~FileHandler() {
        if (fileResource) {
            delete[] (char*)fileResource; 
            fileResource = nullptr;
        }
        std::cout << "File '" << filename << "' closed and resource released." << std::endl;
    }
};

void use_file() {
    FileHandler myFile("log.txt");
    // Object 'myFile' exists and file is open
    // ... do work ...
} // <-- When 'myFile' goes out of scope here, the destructor ~FileHandler() is automatically called.

void demonstrate_destructor() {
    use_file(); // Output shows acquisition and automatic release
    std::cout << "Back in main. File resource is already cleaned up." << std::endl;
}

Chapter 14 Conclusion and Next Steps

Constructors and Destructors complete the basic structure of a robust C++ class. By using them, you guarantee that every object starts in a valid state and ends by safely cleaning up its resources, effectively preventing most memory leaks and dangling pointers.

You now know how to:

  1. Define a class (Chapter 12).
  2. Protect its data (Encapsulation, Chapter 13).
  3. Control its initialization and cleanup (Constructors/Destructors, Chapter 14).

In **Chapter 15**, we will move to the second major pillar of OOP: **Inheritance**. We will learn how to create hierarchical class relationships using the keywords `class`, `public`, and the newly introduced access specifier, `protected`.