C++ Constructors and Destructors: Object Initialization and Cleanup

Default Member Functions in a Class

When a class contains no explicitly defined members, it is called an empty class. However, an empty class is not truly empty. The compiler automatically generates six default member functions if they are not provided by the programmer:

class Date {};  // Compiler generates default: constructor, destructor, copy constructor, copy assignment, address-of (two overloads)

These are known as default member functions — they are generated implicitly when not declared.

Constructors: Initializing Objects

Purpose

Consider the following class that requires a separate Init method to set its data members:

class Date {
public:
    void Init(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }

    void Print() {
        std::cout << _year << "-" << _month << "-" << _day << std::endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main() {
    Date d1;
    d1.Init(2022, 7, 5);
    d1.Print();

    Date d2;
    d2.Init(2022, 7, 6);
    d2.Print();
    return 0;
}

Calling an initialization function after every object creation can be tedious. A constructor is a special member function that has the same name as the class and is automatically invoked when an object is created. Its role is to give each data member an appropriate initial value, and it is called exactly once during the object's lifetime.

Key Characteristics

  1. Name matches class name exactly.
  2. No return type (not even void).
  3. Automatically invoked when an object is instantiated.
  4. Can be overloaded — multiple constructors with different parameter lists.
  5. If no cosntructor is explicitly defined, the compiler generates a default (no-argument) constructor. Once any user-defined constructor is present, the compiler does not generate a default one.
  6. The compiler-generated default constructor initializes custom type members by calling their own default constructors. For built-in type members (e.g., int, char), no initialization is performed by default — they hold indeterminate values. C++11 introduced in-class default member initializers to remedy this:
class MyQueue {
private:
    Stack s1;   // Custom type: Stack must have a default constructor
    Stack s2;
    int size = 0;  // C++11 default initializer
};

Rule of thumb: Write your own constructor when you need specific initialization. Let the compiler generate one only if all data members are custom types with their own constructors or have default initializers.

  1. A default constructor is one that can be called with no arguments — either a no-argument constructor, a constructor where all parameters have default values, or the compiler-generated one. There can be only one default constructor; otherwise, ambiguity arises during object creation.

Destructors: Cleaning Up Resources

Purpose

Whereas constructors handle initialization, destructors handle cleanup. A destructor is automatically called when an object goes out of scope or is explicitly deleted. It does not destroy the object itself (that is managed by the compiler) but releases resources acquired during the object's lifetime, such as dynamical allocated memory or open file handles.

Key Characteristics

  1. Destructor name is the class name preceded by a tilde (~).
  2. Takes no parameters and returns no value.
  3. A class can have only one destructor — overloading is not allowed.
  4. Automatically invoked when the object's lifetime ends (e.g., when a local object goes out of scope).

Example: A simple stack class with dynamic memory:

typedef int DataType;

class Stack {
public:
    Stack(size_t capacity = 3) {
        _array = (DataType*)malloc(sizeof(DataType) * capacity);
        if (!_array) {
            perror("malloc failed");
            return;
        }
        _capacity = capacity;
        _size = 0;
    }

    void Push(DataType data) {
        // CheckCapacity();  // omitted for brevity
        _array[_size] = data;
        ++_size;
    }

    ~Stack() {
        if (_array) {
            free(_array);
            _array = nullptr;
            _capacity = 0;
            _size = 0;
        }
    }

private:
    DataType* _array;
    int _capacity;
    int _size;
};

void TestStack() {
    Stack s;
    s.Push(1);
    s.Push(2);
} // ~Stack() is called automatically here
  1. If no destructor is defined, the compiler generates one that calls the destructors of custom type members (in reverse order of construction). For built-in types, no action is taken.

Summary: Write a destructor when the class manages resources (e.g., dynamic memory, file handles). If all member are custom types with their own destructors, the compiler-generated one suffices.

class MyQueue {
private:
    Stack s1;
    Stack s2;
    int size = 0;
};
// No explicit destructor needed: s1 and s2 are Stack objects with their own destructors.

Tags: C++ constructor destructor object lifecycle default member functions

Posted on Sat, 09 May 2026 11:36:51 +0000 by mantona