C++ tutorials  /  Constructors and Destructors in C++
Chapter 11 · C++

Constructors and Destructors in C++

A constructor in C++ is a special member function that is automatically invoked when an object of a class is created, primarily used to initialize the object's data members. A destructor is a special member function automatically invoked when an object is destroyed (either going out of scope or being explicitly deleted), primarily used to release resources like dynamically allocated memory that the object may have acquired during its lifetime.

Think of a constructor like the setup crew that arrives right before a stage performance begins — they make sure everything (props, lighting, actors) is in place and ready before the show starts, automatically, without anyone needing to remember to call them. A destructor is like the cleanup crew that arrives right after the show ends — they automatically pack everything away and clean up, without anyone having to explicitly remind them. In C++, every time you create an object, its constructor runs automatically to set things up; every time that object's life ends, its destructor runs automatically to clean things up, especially important for releasing memory the object may have dynamically allocated.

A 'DatabaseConnection' class in a web application uses its constructor to automatically open a connection to the database the moment an object is created, and its destructor to automatically close that connection when the object goes out of scope — ensuring connections are never accidentally left open, which could otherwise exhaust the database's connection pool. A 'FileHandler' class similarly opens a file in its constructor and closes it in its destructor, guaranteeing the file is properly released even if an error occurs partway through, as long as the object properly goes out of scope. Video game objects like a 'Particle' effect (an explosion or spark) often use constructors to initialize position, velocity, and lifespan the instant the object is spawned, and destructors to clean up any associated visual resources the moment the effect finishes and the object is destroyed.

Without constructors, every object would need to be manually initialized field-by-field immediately after creation, which is error-prone since a developer might forget a step, leaving the object in an invalid, unpredictable state (garbage values). Without destructors, any resources an object acquires during its lifetime (dynamically allocated memory, open files, network connections, database handles) would need to be manually released every single time an object's usage ends, and forgetting even one such cleanup call across a large codebase leads to resource leaks — constructors and destructors automate this setup and teardown process reliably, which is foundational to the C++ idiom known as RAII (Resource Acquisition Is Initialization).

  • Default Constructor: A constructor that takes no parameters (or has default values for all parameters), automatically provided by the compiler if no other constructor is explicitly defined, used to create an object with default or empty initial values.
  • Parameterized Constructor: A constructor that accepts one or more arguments, allowing an object to be initialized with specific, custom values at the moment of creation rather than defaults, which is the most common constructor type in real-world classes.
  • Copy Constructor: A special constructor that creates a new object as a copy of an existing object of the same class, either automatically provided by the compiler (performing a shallow, member-by-member copy) or explicitly defined by the programmer when deep copying is needed (e.g., for classes managing dynamically allocated memory).
  • Constructor Overloading: Defining multiple constructors within the same class, each with a different parameter list, allowing objects to be created in several different ways depending on which arguments are provided at the point of object creation.
  • Destructor: A special member function (named with a tilde '~' followed by the class name, e.g., '~ClassName()') that is automatically called when an object is destroyed, used to release any resources the object acquired during its lifetime; a class can only have exactly one destructor, which takes no parameters and cannot be overloaded.
class ClassName { private: dataType member; public: // Default constructor ClassName() { member = defaultValue; } // Parameterized constructor ClassName(dataType value) : member(value) {} // Copy constructor ClassName(const ClassName &other) { member = other.member; } // Destructor ~ClassName() { // cleanup logic (e.g., delete dynamically allocated memory) } }; // Usage ClassName obj1; // calls default constructor ClassName obj2(value); // calls parameterized constructor ClassName obj3 = obj2; // calls copy constructor
A developer building a simple resource-tracking class 'Buffer' that dynamically allocates memory in its constructor needs to ensure that memory is properly released in the destructor to avoid leaks, and also needs to understand why the compiler's automatically generated (default) copy constructor is dangerous for this particular class, requiring a custom deep-copy constructor instead.
Constructor and Destructor Managing Dynamic Memory (RAII Pattern)
Demonstrates a class that acquires a resource (dynamically allocated memory) in its constructor and automatically releases it in its destructor, following the RAII idiom.
cpp
#include <iostream> using namespace std; class Buffer { private: int *data; int size; public: Buffer(int s) { size = s; data = new int[size]; cout << "Constructor: Allocated buffer of size " << size << endl; } ~Buffer() { delete[] data; cout << "Destructor: Released buffer memory" << endl; } }; int main() { cout << "Entering scope..." << endl; { Buffer buf(10); cout << "Using buffer inside scope" << endl; } cout << "Exited scope." << endl; return 0; }
Entering scope... Constructor: Allocated buffer of size 10 Using buffer inside scope Destructor: Released buffer memory Exited scope.
When 'buf' is created, its constructor runs automatically, allocating memory with 'new int[size]'. The moment 'buf' goes out of scope (at the closing brace of the inner block), its destructor is automatically invoked, freeing the allocated memory with 'delete[] data;' — this automatic, deterministic cleanup without needing to manually call anything is the core benefit of RAII.
Constructor Overloading for Flexible Object Creation
Shows a class with multiple constructors (default and parameterized), letting objects be created in different ways depending on what information is available at creation time.
cpp
#include <iostream> using namespace std; class Rectangle { private: double length, width; public: Rectangle() { // default constructor length = 1.0; width = 1.0; } Rectangle(double side) { // constructor for a square length = side; width = side; } Rectangle(double l, double w) { // full parameterized constructor length = l; width = w; } double area() { return length * width; } }; int main() { Rectangle r1; Rectangle r2(4.0); Rectangle r3(5.0, 3.0); cout << "r1 area: " << r1.area() << endl; cout << "r2 area: " << r2.area() << endl; cout << "r3 area: " << r3.area() << endl; return 0; }
r1 area: 1 r2 area: 16 r3 area: 15
Three different constructors are defined with different parameter lists — the compiler automatically selects the correct one based on the number of arguments used when each object is created: 'r1' uses the no-argument default constructor, 'r2' uses the single-argument constructor (treating it as a square), and 'r3' uses the two-argument constructor for a full custom rectangle.
  • Relying on the Compiler's Default (Shallow) Copy Constructor for Classes with Pointers: If a class contains a raw pointer to dynamically allocated memory and doesn't define its own copy constructor, the compiler-generated default copy constructor performs a 'shallow copy' — it copies the pointer's address, NOT the data it points to. This means two objects end up pointing to the SAME memory block, and when one object's destructor deletes that memory, the other object is left with a dangling pointer, causing a crash or undefined behavior when it's later used or its own destructor runs (a 'double delete').
  • Not Defining a Destructor When the Class Manages Dynamic Memory: If a class dynamically allocates memory (via 'new') in its constructor but doesn't define a destructor to 'delete' it, that memory is never freed when the object is destroyed, causing a memory leak every single time an object of that class goes out of scope or is deleted.
  • Confusing Object Copy-Initialization with Assignment: Beginners often confuse 'ClassName obj2 = obj1;' (which invokes the COPY CONSTRUCTOR, since obj2 doesn't exist yet) with 'obj2 = obj1;' where obj2 already exists (which invokes the ASSIGNMENT OPERATOR '=', a completely different special member function). Classes managing dynamic memory typically need custom versions of both to avoid the same shallow-copy pitfalls in either scenario.
  • Defining Multiple Constructors That Create Ambiguity: Overloading constructors with parameter types that are too similar (e.g., one taking an 'int' and another taking a 'double') can create ambiguous calls when an argument doesn't exactly match either type (like passing a 'float'), causing a compile-time error since the compiler can't determine which overload was intended.
  • Forgetting That Constructors/Destructors Run Automatically and Trying to Call Them Manually: Beginners sometimes try to explicitly call a constructor or destructor directly like a regular function (e.g., 'obj.ClassName();'), not realizing these are invoked automatically by the language at object creation/destruction — manually calling a destructor directly (without using 'delete' on a heap object) can lead to it being called again automatically later, causing undefined behavior from a double destruction.
  • Follow the Rule of Three (or Rule of Five) When Managing Resources: If a class needs a custom destructor (because it manages a resource like dynamic memory), it almost certainly also needs a custom copy constructor and a custom copy assignment operator (the 'Rule of Three') to avoid shallow-copy bugs; in modern C++11 and later, this extends to the 'Rule of Five', additionally including a move constructor and move assignment operator for efficient resource transfer.
  • Prefer Member Initializer Lists Over Assignment in the Constructor Body: Use 'ClassName(int x) : memberX(x) {}' instead of 'ClassName(int x) { memberX = x; }' — initializer lists directly construct members with their intended values (more efficient than default-constructing then reassigning), and are mandatory for initializing 'const' members, reference members, and members without a default constructor.
  • Use Smart Pointers to Avoid Needing Manual Destructors for Memory Management: Where possible, use 'std::unique_ptr' or 'std::shared_ptr' as data members instead of raw pointers requiring manual 'new'/'delete' — smart pointers automatically handle proper cleanup in their own destructors, often eliminating the need to write a custom destructor (or copy constructor) for your class at all, following modern C++ best practices.
  • Keep Constructors Focused on Initialization, Not Complex Business Logic: A constructor's job is to establish a valid initial state for the object — avoid putting heavy computation, I/O operations, or complex business logic inside a constructor, since exceptions thrown from constructors can be tricky to handle correctly, and keeping constructors simple makes object creation predictable and easy to reason about.
What is a constructor in C++, and what are its key characteristics?
A constructor is a special member function automatically invoked when an object is created, primarily used to initialize the object's data members. Key characteristics include: it has the exact same name as the class, it has no return type (not even 'void'), it can be overloaded (multiple constructors with different parameter lists), and if no constructor is explicitly defined, the compiler automatically generates a default constructor (though this compiler-generated version disappears once any constructor is explicitly defined by the programmer).
What is the difference between a shallow copy and a deep copy, and why does it matter for classes containing pointers?
A shallow copy copies the values of an object's data members directly, including copying a pointer's address itself rather than the data it points to — meaning both the original and copied object end up pointing to the exact same underlying memory. A deep copy, by contrast, allocates a completely separate, independent block of memory for the copy and copies the actual data into it, so the two objects have their own distinct memory. This matters because with a shallow copy, when one object's destructor frees the shared memory, the other object is left with a dangling pointer, and if both destructors run, the same memory gets deleted twice — undefined behavior that a custom deep-copy constructor is needed to prevent.
What is the 'Rule of Three' in C++?
The Rule of Three states that if a class requires a custom destructor, a custom copy constructor, or a custom copy assignment operator, it almost certainly requires all three. This typically arises when a class manages a resource (like dynamically allocated memory) that the compiler's default (shallow-copying) versions of these functions would handle incorrectly — defining just one without the other two usually leads to bugs like double-deletion or memory leaks, so all three should be defined together consistently.
Can a destructor be overloaded in C++? Why or why not?
No, a class can only have exactly one destructor, and it cannot be overloaded, because a destructor takes no parameters and no return type, meaning there's no way to differentiate multiple versions even if the language allowed it. Unlike constructors, which can vary by parameter list to support different initialization scenarios, a destructor's job — cleaning up the object right before its memory is reclaimed — is always the same single operation regardless of how the object was created, so only one destructor definition is possible per class.
In what order are constructors and destructors called when dealing with multiple objects, especially in inheritance hierarchies?
For simple, unrelated objects, constructors are called in the order the objects are declared/created, and destructors are called in the exact REVERSE order of construction (last created, first destroyed) — following a stack-like Last-In-First-Out pattern for objects in the same scope. In inheritance hierarchies, when a derived class object is created, the BASE class's constructor runs first (setting up the inherited portion), followed by the DERIVED class's constructor; destruction happens in the opposite order — the derived class's destructor runs first, followed by the base class's destructor, ensuring resources are cleaned up in the reverse order they were acquired.
Write a 'Logger' class whose constructor prints "Logger started" and whose destructor prints "Logger stopped", then create an object inside a limited scope block to observe the automatic call order.
#include <iostream> using namespace std; class Logger { public: Logger() { cout << "Logger started" << endl; } ~Logger() { cout << "Logger stopped" << endl; } }; int main() { cout << "Before scope" << endl; { Logger log; cout << "Inside scope" << endl; } cout << "After scope" << endl; return 0; }
Write a 'DynamicArray' class that dynamically allocates an int array in its constructor, properly deletes it in its destructor, and defines a custom copy constructor that performs a deep copy (not a shallow one).
#include <iostream> using namespace std; class DynamicArray { private: int *data; int size; public: DynamicArray(int s) { size = s; data = new int[size]; for (int i = 0; i < size; i++) data[i] = 0; } // Deep copy constructor DynamicArray(const DynamicArray &other) { size = other.size; data = new int[size]; for (int i = 0; i < size; i++) data[i] = other.data[i]; } ~DynamicArray() { delete[] data; } }; int main() { DynamicArray arr1(5); DynamicArray arr2 = arr1; // deep copy, not sharing memory cout << "Both arrays created with independent memory." << endl; return 0; }
Create a class with three overloaded constructors (default, single-argument, and two-argument) and demonstrate creating one object with each version.
#include <iostream> using namespace std; class Box { private: int length, width; public: Box() { length = 1; width = 1; } Box(int side) { length = side; width = side; } Box(int l, int w) { length = l; width = w; } void display() { cout << length << " x " << width << endl; } }; int main() { Box b1; Box b2(4); Box b3(6, 2); b1.display(); b2.display(); b3.display(); return 0; }

Constructors and destructors automate the initialization and cleanup of C++ objects, forming the basis of the RAII idiom that ties resource management directly to an object's lifetime. Understanding the different constructor types — default, parameterized, and copy constructors — along with the critical distinction between shallow and deep copying, and following the Rule of Three when managing resources like dynamic memory, are essential skills for writing safe, leak-free C++ classes. Mastering these concepts ensures objects are always created in a valid state and cleaned up reliably, setting the stage for more advanced OOP topics like inheritance and polymorphism.

© 2026 CompileX. Maintained by Aditya Kumar Sharma.