Page Content

Tutorials

Constructors and Destructors In Inheritance C++ Explained

Constructors and Destructors in Inheritance C++

Constructors and destructors are unique member functions in C++ that control an object’s lifecycle, including initialization and cleaning. Their behaviour becomes especially significant when inheritance is included since they control the creation and destruction of base and derived class components of an object.

Constructors in Inheritance

A constructor is a member function that doesn’t return a value but shares the same name as the class type. Its main responsibility is to initialize a class object’s data elements. Different approaches to object initialization can be enabled by overloading constructors.

A sub-object that corresponds to the base class(es) is included in a derived class object when it is created. Consequently, this base sub-object must be created and initialized by calling the base class constructor before the body of the derived class constructor is run. From the most basic class to the most derived class, constructors are thus run in the sequence of derivation.

Features

Important features of constructors in inheritance

Initialization Order: A class object is constructed “from the bottom up” by a constructor, which calls its base class constructors first, then its member constructors, and lastly its own body.

Passing Parameters to Base Constructors: A constructor initialiser list is used by the derived-class constructor to give inputs to a base-class constructor. This list, which contains the arguments for the base class constructor(s), comes after a colon after the declaration of the derived constructor. If a base class constructor is not called explicitly, the compiler will invoke the base class’s default constructor as the derived class constructor’s initial action.

Delegating Constructors: Delegating constructors, which made code simpler by enabling one constructor in a class to invoke another constructor in the same class, was first introduced in C++11.

Inherited Constructors: According to the new standard, a derived class that provides a using declaration naming its base class is able to reuse constructors provided by its direct base class. As a result, each base constructor with the same argument list has derived constructors generated by the compiler. Nevertheless, this is not how the default, copy, and move constructors are inherited; instead, the derived class synthesises them if it does not specify them explicitly.

Constructor Example:

#include <iostream>
class Base {
public:
    int base_val;
    Base(int x) : base_val(x) { // Base constructor with parameter
        std::cout << "Constructing Base with value: " << base_val << std::endl; 
    }
    Base() : base_val(0) { // Default Base constructor
        std::cout << "Constructing Base (default)" << std::endl; 
    }
};
class Derived : public Base {
public:
    int derived_val;
    // Derived constructor calling a specific Base constructor using initializer list
    Derived(int x, int y) : Base(x), derived_val(y) { 
        std::cout << "Constructing Derived with value: " << derived_val << std::endl; 
    }
    // Derived constructor calling default Base constructor (implicitly)
    Derived(int y) : derived_val(y) {
        std::cout << "Constructing Derived (only derived val): " << derived_val << std::endl; 
    }
};
int main() {
    std::cout << "Creating Derived object ob1(10, 20):" << std::endl;
    Derived ob1(10, 20); // Calls Base(10), then Derived(10, 20)
    std::cout << "ob1.base_val: " << ob1.base_val << ", ob1.derived_val: " << ob1.derived_val << std::endl;
    std::cout << std::endl;
    std::cout << "Creating Derived object ob2(30):" << std::endl;
    Derived ob2(30); // Calls Base() (default), then Derived(30)
    std::cout << "ob2.base_val: " << ob2.base_val << ", ob2.derived_val: " << ob2.derived_val << std::endl;
    return 0;
}

Output

Creating Derived object ob1(10, 20):
Constructing Base with value: 10
Constructing Derived with value: 20
ob1.base_val: 10, ob1.derived_val: 20
Creating Derived object ob2(30):
Constructing Base (default)
Constructing Derived (only derived val): 30
ob2.base_val: 0, ob2.derived_val: 30

Destructors in Inheritance

A destructor, a member function followed by a tilde (~), is invoked to clear up resources used by an object upon destruction. Classes can only have one destructor because they don’t take arguments or return values.

The destructor is automatically called when an object is destroyed. Local objects are destroyed at the end of its code block; global and static objects are destroyed at program end.

In reverse order, base class destructors are invoked after derived class destructors. The base class destructor is automatically called after the derived class destructor.

Importance

The significance of virtual destroyers

Polymorphic Destruction: A virtual destructor must be defined by a class if it contains any virtual functions. When allocating objects dynamically in an inheritance structure, this is essential. Because the derived portion of the object would not be appropriately destroyed, a non-virtual base class destructor would only invoke the base class destructor if a base class pointer to a derived class object was erased. This might result in “object slicing” and possible memory leaks. The base class destructor is called after the appropriate derived class destructor, with a virtual destructor.

Synthesized Move Operations: The compiler cannot synthesise move constructor and move assignment operators for that class if a destructor is defined, even if it is defaults. This has a substantial indirect impact. It is then necessary to define move operations explicitly if they are desired.

Destructor Example:

#include <iostream>
class Base {
public:
    Base() {
        std::cout << "Constructing Base\n"; 
    }
    // Made virtual to ensure proper derived class destruction
    virtual ~Base() {
        std::cout << "Destructing Base\n"; 
    }
};
class Derived : public Base {
public:
    Derived() {
        std::cout << "Constructing Derived\n"; 
    }
    ~Derived() override { // Using 'override' keyword for clarity (C++11)
        std::cout << "Destructing Derived\n"; 
    }
};
int main() {
    std::cout << "Creating Derived object on stack:\n";
    Derived stackObj; // Constructor: Base, Derived
    std::cout << "Stack object scope ending.\n"; // Destructor: Derived, Base (implicitly called)
    std::cout << std::endl;
    std::cout << "Creating Derived object on heap via Base pointer:\n";
    Base* heapPtr = new Derived(); // Constructor: Base, Derived
    std::cout << "Deleting heap object via Base pointer.\n";
    delete heapPtr; // Destructor: Derived, Base (because Base destructor is virtual)
    std::cout << std::endl;
    // Example of what would happen WITHOUT virtual destructor (conceptually)
    // If ~Base() was NOT virtual:
    // Base* problematicPtr = new Derived();
    // delete problematicPtr; // Only ~Base() would be called, ~Derived() would be skipped.
                            // This leads to "object slicing" and potential resource leaks.
    return 0;
}

Output

Creating Derived object on stack:
Constructing Base
Constructing Derived
Stack object scope ending.
Creating Derived object on heap via Base pointer:
Constructing Base
Constructing Derived
Deleting heap object via Base pointer.
Destructing Derived
Destructing Base
Destructing Derived
Destructing Base

Finally, C++ handles inheritance hierarchies’ constructor and destructor calls automatically. Destructors disassemble stuff from the top down, whereas constructors build it. To provide polymorphic behaviour and resource management with dynamically allocated objects, base classes must include virtual destructors.

You can also read What Is Mean By Is A Relationship In C++ With Code Example?

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index