Skip to Content
Course content

24: Constructors and Destructors

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Think about hiring a new employee for a company. You don't just tell them "start working" the second they walk through the door. There's an onboarding process: you give them a desk, a laptop, a security badge, and an email address. This ensures that by the time they actually start their first task, they have everything they need to function. When that employee eventually leaves the company, you do the opposite. You collect the laptop, revoke the badge access, and close the email account. If you forget this "offboarding" part, you've got a security risk and wasted hardware.

In C++, constructors and destructors are exactly that. The constructor is the onboarding—it sets the object up for success. The destructor is the offboarding—it cleans up the mess so your program doesn't leak memory or leave files open.

The Setup Phase: Constructors

A constructor is a special member function that runs automatically the moment you create an object. It has the same name as the class and no return type. I usually tell people to think of the constructor as a "guarantee." By the time the constructor finishes, the object should be in a valid, usable state. You shouldn't have to call a separate init() function after creating an object; that's a recipe for bugs because someone will inevitably forget to call it.

class FileLogger {
private:
    std::string filename;
    std::ofstream logFile;

public:
    // This is the constructor
    FileLogger(std::string name) : filename(name) {
        logFile.open(filename, std::ios::app);
        std::cout << "Log file " << filename << " opened for writing.\n";
    }
    
    void log(std::string message) {
        logFile << message << std::endl;
    }
};

Notice that : filename(name) part? That's called a member initializer list. Use it. It's more efficient than assigning values inside the curly braces because it initializes the member directly rather than creating it and then assigning a value to it. It's a small habit that separates the pros from the amateurs.

Different Ways to Get Started

You aren't limited to just one way of starting an object. You can "overload" constructors. Maybe sometimes you want to provide a filename, and other times you want the class to just use a default "system.log".

class FileLogger {
    // ... other members ...
public:
    // Default constructor
    FileLogger() : FileLogger("system.log") {
        // I'm using 'constructor delegation' here to avoid repeating code
    }

    // Parameterized constructor
    FileLogger(std::string name) : filename(name) {
        logFile.open(filename, std::ios::app);
    }
};

Cleaning Up After Yourself: Destructors

Now, here is where things get critical. If your class manages a resource—like a file handle, a network socket, or raw memory allocated with new—you must have a destructor. The destructor is called automatically when the object goes out of scope (like when a function ends) or when you explicitly delete it.

The destructor has the same name as the class but is preceded by a tilde (~). It takes no arguments and returns nothing.

class FileLogger {
    // ... constructors and methods ...

public:
    ~FileLogger() {
        if (logFile.is_open()) {
            logFile.close();
        }
        std::cout << "Log file closed and resources released.\n";
    }
};

If you forget to close that file in the destructor, you might find your OS complaining that too many files are open, or you might lose the last few lines of data that were sitting in the buffer. In the world of raw pointers, forgetting a destructor that calls delete is how you get those dreaded memory leaks that crash servers after three days of uptime.




📋 Practical Task

Build a Dynamic Integer Array Wrapper

Your task is to create a class called IntArray that manages a heap-allocated array of integers. This will force you to handle manual memory management using a constructor and destructor.

  • The Constructor: It should take an integer size as an argument and allocate an array of that size using new int[size]. Store the size in a member variable.
  • The Destructor: It must use delete[] to free the allocated memory to prevent a memory leak.
  • A Helper Method: Add a method setValue(int index, int value) to populate the array and getValue(int index) to retrieve a value.

In your main() function, create an IntArray object inside a nested scope (using curly braces { }). Once the code execution leaves that scope, your destructor should trigger. Add a std::cout statement inside the destructor so you can actually see it happening in the console.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.