Explore Constructors and Destructors in C++

C++ programmers have two basic responsibilities: one, allocate memory for variables defined and two, deallocate the space gracefully once it is no longer used. It is not difficult to guess what havoc it can cause when unused memory is not freed up properly. Unlike other high-level languages like Java, which has its own garbage collector to do the clean-up job, C++ is not that kind. Here, it is completely at the discretion of the programmer who solely decides how efficiently the memory may be used. This is a huge responsibility, no doubt. Object allocation is performed with the help of a special function, called a constructor, and the deallocation process is handled by a function called a destructor. Both are basically the member functions of a class. This article explores the concepts behind them, along with their implementations in C++.

Construct of a Constructor

A constructor is basically a function, just like any other function in C++, but in contrast to other member functions of the class, the following rules specifically apply to it:

  • A constructor name must be same as the class name.
  • A constructor never returns any value, not even void.

In C++, each call to object creation is backed by its constructor call, be it explicitly or implicitly.

#ifndef ACCOUNT_H
#define ACCOUNT_H
#include <string>
using namespace std;
class Account
{
   private:
      unsigned long account_no;
      string name;
      double balance;
   public:
      Account();
      Account(unsigned long);
      Account(unsigned long, const string&, double=0.0);
      Account(const string&, unsigned long, double);
      void display();
      ~Account();
};
#endif   // ACCOUNT_H


#include <iostream>
#include <iomanip>
#include "Account.h"

Account::Account()
{
   account_no=0;
   name="";
   balance=0.0;
}

Account::Account(unsigned long new_acno){
   account_no=new_acno;
}


Account::Account(unsigned long new_acno, const string& new_name,
      double new_balance){
   account_no=new_acno;
   name=new_name;
   balance=new_balance;
}

Account::Account(const string& new_name, unsigned long new_acno,
      double new_balance){
   account_no=new_acno;
   name=new_name;
   balance=new_balance;
}

void Account::display(){
   cout<<"Account No: "<<account_no<<endl;
   cout<<"Name: "<<name<<endl;
   cout<<"Balance: "<<balance<<endl;
   cout<<endl;
}

Account::~Account()
{

}

#include <stdio.h>
#include "Account.h"

int main(int argc, char **argv)
{
   Account a1;
   Account a2(111,"Parmer");
   Account a3("Sally", 222, 6748.90);
   a1.display();
   a2.display();
   a3.display();

   Account *pa1=new Account();
   Account *pa2=new Account(123,"Holmes");
   Account *pa3=new Account("Jack", 456, 7896.90);
   pa1->display();
   pa2->display();
   pa3->display();

   Account a5=987;
   a5.display();

   return 0;
}

Constructors are generally declared in the public section of a class and is called when an object of the class is created in the following manner:

Account a1;
Account a2(111,"Parmer");
Account a3("Sally", 222, 6748.90);

Or, we may use a pointer to create the object dynamically with the help of the new operator:

Account *pa1=new Account();
Account *pa2=new Account(123,"Holmes");
Account *pa3=new Account("Jack", 456, 7896.90);

If a constructor is defined with a single parameter, such as:

Account(unsigned long);

We can initialize it as follows:

Account a4=765;

For example, the constructor call

   Account a2(111,"Parmer");
   Account *pa2=new Account(123,"Holmes");

is internally represented as

   Account a2;
   a2.Account::Account(111,"Parmer");
   try{
      Account *pa2=_new(sizeof(Account));
      pa2->Acct.Account::Account(111,"Holmes");
   }catch(std::bad_alloc){
      //operation new failed
   }

If the constructor is defined inline, it is expanded at the point of invocation.

A constructor can be overloaded, just like any C++ function, and constructors are distinguished on the basis of type, number, and order of the parameters. Apart from memory allocation in the heap area, a constructor is used to initialize objects. Initialization of an object means that the class member variable has valid data. An uninitialized object, on the other hand, can create a problem at run time. C++ implicitly initializes objects with the help of a constructor as soon as it is defined and makes it ready with valid data to work on. A constructor also can be used in the validation process such that the member variables have right values before actual initialization takes place; otherwise, they can be initialized with default values. These features enable a constructor to do more complex tasks, such as opening files, database registration, driver configuration, and so forth, depending upon the objective of the class design.

If a constructor is defined without any parameters, it is called a default constructor. Upon calling the default constructor, it initializes the member variables with standard values such as 0 for int type and null for object references variables. Also, if no explicit constructor is provided, C++ implicitly provides a public constructor. If at least one constructor is provided, C++ refrains from supplying any implicit constructor. In practice, it is almost always necessary to provide a default constructor even if other constructors are defined.

There is another type of constructor, called a copy constructor. It is used to initialize one class object with another object of its class. Copy constructors are defined in the following manner:

class Account{
   // ...
   Account(const Account& anotherAccount);
   // ...
};

Construct of a Destructor

Every object created by the constructor allocates some space in memory. This area must be freed up with a cleaning process called a destructor. A destructor mainly releases allocated memory space and then closes any open files, which is an overall freeing up of resources so that the memory space can be reused. Much like the constructor, a destructor is also a function with following rules:

  • A destructor name must be same as class name preceded by a ~ (tilde).
  • A destructor never takes any parameters. So, there’s no question of overloading.
  • A destructor returns nothing.
  • A destructor is called automatically at the end of an object’s lifetime. It is called:
    • At the end of the program for static or global objects.
    • And for the local objects, at the end of the block that define the object.

It is interesting to note that the destructor name begins with a ~ (tilde) symbol. A tilde is a Boolean complement operator in C++. So, it intuitively means NOT/complement constructor = destructor. For example, a destructor declaration in a class is as follows:

class Account{
   public:
   ~Account();
};

Suppose we declare a character array for name in the Account class:

class Account{
   private:
   char name[50];
   //...
};

then the right way to deallocate the space occupied during object construction is written as follows:

Account::~Account(){
   delete[] name;
}

The array symbol with the delete operator denotes that there are consecutive allocations of memory that need to be freed up.

As the new operator helps to control the allocation process, in much the same way delete operator helps control the deallocation process. These two are the key operators used in dynamic memory management. The new operator invokes the constructor explicitly and the delete operator invokes the destructor. For example, we may construct a object:

Person *ptr=new Person();

This will call the default constructor. On the other hand, when we write

delete ptr;

The destructor of the Person class in invoked.

Conclusion

Understanding the memory allocation and deallocation processes is crucial in C++ programming. Constructors and destructors are the two vital points of this paradigm. Unlike in C, where we use the cryptic malloc and calloc operations for dynamic memory allocation and the free() method for memory deallocation, C++ is quite simple in this respect. Although the underlying principle is similar, C++ provides better control with the help of methods like constructors and destructors.

References

  • Lippman and Lajoie. C++ Primer, Third Edition. Pearson.
  • Dietel and Dietel. C++ How to Program, Fifth Edition. Pearson.
Manoj Debnath
Manoj Debnath
A teacher(Professor), actively involved in publishing, research and programming for almost two decades. Authored several articles for reputed sites like CodeGuru, Developer, DevX, Database Journal etc. Some of his research interest lies in the area of programming languages, database, compiler, web/enterprise development etc.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read