C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
24: Constructors and Destructors
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
sizeas an argument and allocate an array of that size usingnew 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 andgetValue(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.
There are no comments for now.