Constructors & Destructors in C++

Go through all  (Must Read *) if you don't have enough time.

C++ doesn't initialise variables automatically unlike Java. Even the class members are not initialised. So, the programmer has to define a function (user-defined function) to initialise the data members. 

Let's recap the class and its members before jump into the constructors.

Program 1

class BookClub {

   public:
       void enroll(char name[]);
       void deposit(int id);

   private:
      char members_ [30]; 
      int  memberID_;
};

This is a simple class,  and _suffix used to indicate the privateness. 

Class can be used in different ways. 

1. Declare a variable of class type. 

BookClub mem1;

2. Pass arguments of class type. 

contestRun(BookClub club1);

3. Utilise class type to build other types. 

class Bookstagrammer {

   private:
      char members_ [30]; 
      int  memberID_;
      BookClub club_ [30];
};

For the below program,

Program 2

#include <iostream>
using namespace std;

// Ideally this should be inside .h file
class BookClub {

   public:
       void enroll(char name[]);
       void deposit(int id);
       char* list();

   private:
      char members_ [30]; 
      int  memberID_;
};

// This should be in .cpp file 
int main()
{
    // Object Initialisation for the class BookClub
    BookClub mem1;
    char name[30];
    int id;
    
    mem1.enroll(name);
    mem1.deposit(id);
    cout << mem1.list();

    return 0;
}

Before using the members_ & memberID_, these members need to be initialised. What are the solutions?

1. Programmer defined init function 

Program 3

int main()
{
    // Object Initialisation for the class BookClub
    BookClub mem1; // Memory is allocated
    char name[30];
    int id;
    
    mem1.initBookClub(name, id);   // User defined initialisation
    mem1.enroll(name);
    mem1.deposit(id);
    cout << mem1.list();

    return 0;
}

What if the user forgot to initialise this BookClub? 

2. Automatic initialisation method for class members using Constructor. 

Constructor (Interview - Must Read *)

A constructor is a function automatically/implicitly called when a class instance is declared. 

  • Constructor name is a class name, and it doesn't have any explicit return type and not even void. 
  • It is invoked automatically when the object is created. 
  • Constructor should be inside public section so that class can be instantiated from anywhere. 
  • This is also a method of initialising the members of the class. 
  • Constructors can take arguments as well. 
  • There can be multiple constructors with the same name as class but distinguished by types of the arguments we pass. This means constructor can be overloaded.  
  • Constructor can't be invoked using the dot notation. 
  • Constructor can not be virtual, because when constructor of a class is executed there is no vtable in the memory, means no virtual pointer defined yet. Hence the constructor should always be non-virtual.



Program 4  (Interview - Must Read *)
#include <iostream>

// User-defined type
class Foo
{
public:
    
    Foo(int i);   // Constructor Declaration
    Foo() { //  Constructor Definition inside class
        _i = 7;
    }
    
    int GetValue() const
    {
        return _i;
    }
private:
    int _i;
};

//  Constructor Definition outside class
Foo::Foo(int i)   
{ 
   _i = i;
} 

int main()
{
    Foo foo(5);
    std::cout << "Parameterised Ctor - The value of foo is " << foo.GetValue() << std::endl;
    
    Foo fooDef;
    std::cout << "Default Ctor - The value of foo is " << fooDef.GetValue() << std::endl;
}

Output:
1
2
Parameterised Ctor - The value of foo is 5
Default Ctor - The value of foo is 7

Program 5

#include <iostream>
using namespace std;

class BookClub {

   public:
       BookClub(); // Constructor or zero-parameterised constructor
       void enroll(char name[]);
       void deposit(int id);
       char* list();

   private:
      char members_ [30]; 
      int  memberID_;
};

/* Definition of a Constructor */
BookClub::BookClub() {
   strcpy(members_, "");
   memberID_ = 0;
}

int main()
{
    // Object Initialisation for the class BookClub
    BookClub mem1; // Constructor is called automatically
    char name[30] = "Shirley";
    int id = 5;
    mem1.enroll(name);
    mem1.deposit(id);
    cout << mem1.list();

    return 0;
}


Program 6
#include <iostream>
#include <cstdio>
using namespace std;

class BookClub {

   public:
       BookClub(); // Constructor 
       void enroll(char name[]);
       void deposit(int id);
       char* list();

   private:
      char members_ [30]; 
      int  memberID_;
};

/* Definition of a Constructor */
BookClub::BookClub() {
   strcpy(members_, "");
   memberID_ = 0;
}

void BookClub::enroll(char name[]) {
   strcpy(members_, name);
}

void BookClub::deposit(int id) {
   memberID_ = id;
}

char* BookClub::list() {
   char* buffer = new char[100];
   int buf_size = 100;
   snprintf(buffer, buf_size, "MemberName : %s MemberID : %d ", 
            members_, memberID_);
   return buffer;
}

int main()
{
    // Object Initialisation for the class BookClub
    BookClub mem1; // Constructor is called automatically
    char name[30] = "Shirley";
    int id = 5;
    mem1.enroll(name);
    mem1.deposit(id);
    cout << mem1.list();

    return 0;
}

1
MemberName : Shirley MemberID : 5 

Initialising the members inside the body {} is a old school method.

As stated earlier, constructor is implicitly invoked when the object is created.  We can't assign constructor to another constructor using assignment operator because it doesn't have any return value.

BookClub mem2 =  BookClub("Sivy");         //This is wrong                       

Note: If there is no arguments for constructor, just create object instance as 

 BookClub mem1; 

Don't do  BookClub mem1();                                  

Because this is function declaration, but not object creation/initialisation. 

Implementation of multiple constructors 

Program 7
#include <iostream>
#include <cstdio>
using namespace std;

class BookClub {

   public:
       BookClub(); // Default Constructor 
       BookClub(char name[]); 
       BookClub(char name[], int id);
       void enroll(char name[]);
       void deposit(int id);
       char* list();

   private:
       char members_ [30]; 
       int  memberID_;
};

/* Definition of a Default Constructor */
BookClub::BookClub() {
   strcpy(members_, "");
   memberID_ = 0;
}

/* Definition of a Constructor */
BookClub::BookClub(char name[]) {
   strcpy(members_, name);
   memberID_ = 0;
}

/* Definition of a Constructor */
BookClub::BookClub(char name[], int id) {
   strcpy(members_, name);
   memberID_ = id;
}

void BookClub::enroll(char name[]) {
   strcpy(members_, name);
}

void BookClub::deposit(int id) {
   memberID_ = id;
}

char* BookClub::list() {
   char* buffer = new char[100];
   int buf_size = 100;
   snprintf(buffer, buf_size, "MemberName : %s MemberID : %d ", 
            members_, memberID_);
   return buffer;
}

int main()
{
    // Object Initialisation for the class BookClub
    BookClub mem1; // Constructor is called automatically    
    BookClub mem2("Sivy");
    BookClub mem3("Rose", 4);
char name[30] = "Shirley"; int id = 5; mem1.enroll(name); mem1.deposit(id); cout << mem1.list(); return 0; }


Here, I didn't specify the specific constructor, so the compiler had taken the default constructor which has no arguments. 

What if I don't have the default constructor itself ?

In the below program, I don't have a default constructor, so the compiler complaints

Program 8

#include <iostream>
#include <cstdio>
using namespace std;

class BookClub {

   public:
       //BookClub(); // Default Constructor 
       BookClub(char name[]); 
       BookClub(char name[], int id);
       void enroll(char name[]);
       void deposit(int id);
       char* list();

   private:
       char members_ [30]; 
       int  memberID_;
};

/* Definition of a Default Constructor */
/*BookClub::BookClub() {
   strcpy(members_, "");
   memberID_ = 0;
}*/

/* Definition of a Constructor */
BookClub::BookClub(char name[]) {
   strcpy(members_, name);
   memberID_ = 0;
}

/* Definition of a Constructor */
BookClub::BookClub(char name[], int id) {
   strcpy(members_, name);
   memberID_ = id;
}

void BookClub::enroll(char name[]) {
   strcpy(members_, name);
}

void BookClub::deposit(int id) {
   memberID_ = id;
}

char* BookClub::list() {
   char* buffer = new char[100];
   int buf_size = 100;
   snprintf(buffer, buf_size, "MemberName : %s MemberID : %d ", 
            members_, memberID_);
   return buffer;
}

int main()
{
    // Object Initialisation for the class BookClub
    BookClub mem1; // Constructor is called automatically
    char name[30] = "Shirley";
    int id = 5;
    mem1.enroll(name);
    mem1.deposit(id);
    cout << mem1.list();

    return 0;
}

Output:

1
2
3
In function 'int main()':
Line 57: error: no matching function for call to 'BookClub::BookClub()'
compilation terminated due to -Wfatal-errors.

This would be more common problem in inheritance. 

Constructors' can't be inherited. 

Consider primitive data type as constructor. 

int num;   // Default constructor is called

int num(0);  // It accepts one argument and initialises the integer to the passed value 

int num = 0; // This is called copy constructor 

There is another problem if the constructors are not called cautiously or sequentially.

Program 9

class Bookstagrammer {
      BookClub mem3; // mem3 object creation
      BookClub mem4;

      Boostagrammer() {
          mem3("Dolly"); // Before reaching here
          mem4("Agnes", 45);
      }
};

class BookClub {
   public:
       BookClub(); // Default Constructor 
       BookClub(char name[]); 
       BookClub(char name[], int id);
       void enroll(char name[]);
       void deposit(int id);
       char* list();

   private:
       char members_ [30]; 
       int  memberID_;
};

In the above example, when the Bookstagrammer object is created, it will call the BookClub constructor as there are mem3 and mem4 variables in Bookstagrammer class. So, before calling the Bookstagrammer constructor "Before reaching here" the variables mem3 and mem4 is initialised with the help of BookClub Constructor. This is a kind of duplication.  

If we don't have the BookClub default constructor, we will again be into trouble. 

Parameterised Constructor  (Member Initialisation List)   (Must Read *)

Having the member initialiser list makes the computation fast than initialising the members in the body of the constructor. Click here to read about constructor initialiser list

Program 10  (Interview - Must Read *)

#include <iostream>

// User-defined type
class Foo
{
public:
    
    Foo(int i);   // Constructor Declaration
    Foo() : _i(7) {}  // Constructor Definition
                      // ": _i(5)" is initialiser list
                      // ": _i(5) {}" is function body
    
    int GetValue() const
    {
        return _i;
    }
private:
    int _i;
};

/* Constructor Definition 
 * where ": _i(i)" is initialiser list
 */
Foo::Foo(int i) : _i(i) 
{ 
   /* std::cout << " Foo - Parametersised Ctor" 
               << std::endl;  */ 
} 

int main()
{
    Foo foo(5);
    std::cout << "Parameterised Ctor - The value of foo is " 
              << foo.GetValue() << std::endl;
    
    Foo fooDef;
    std::cout << "Default Ctor - The value of foo is " 
              << fooDef.GetValue() << std::endl;
    return 0;
}

Output:
1
2
Parameterised Ctor - The value of foo is 5
Default Ctor - The value of foo is 7

This is to avoid invoking the constructor twice. 

Program 11

class Bookstagrammer {
      BookClub mem3; // mem3 object creation
      BookClub mem4;

      Boostagrammer(): mem3("Dolly"), mem4("Agnes") {
          // Before reaching here    
      }
};

class BookClub {
   public:
       BookClub(); // Default Constructor 
       BookClub(char name[]); 
       BookClub(char name[], int id);
       void enroll(char name[]);
       void deposit(int id);
       char* list();

   private:
       char members_ [30]; 
       int  memberID_;
};

In the comment line 'Before reaching here', the member variables are initialised using the parameterised constructor.

Program 12

class Bookstagrammer {
      BookClub mem3; // mem3 object creation
      BookClub mem4;

      Boostagrammer(): memberID_(0.0) {
          // Before reaching here    
      }
};

class BookClub {
   public:
       BookClub(); // Default Constructor 
       BookClub(char name[]); 
       BookClub(char name[], int id);
       void enroll(char name[]);
       void deposit(int id);
       char* list();

   private:
       char members_ [30]; 
       int  memberID_;
};

It is good to use this parameterised constructor, otherwise there will be a situation, you can't initialise the parent class properly. 

In the below example, pointer is created for the class, but the memory is not allocated.

Program 13   (Must Read *)

int main()
{
    // Object Initialisation for the class BookClub
    BookClub mem1; // Constructor is called automatically
    BookClub mem2("Sivy");
    BookClub mem3("Rose", 4);
    BookClub *mem6;  // Constructor will not be called

    char name[30] = "Shirley";
    int id = 5;
    mem1.enroll(name);
    mem1.deposit(id);
    cout << mem1.list();

    return 0;
}

Memory will be allocated only when the new is used, and the constructor is also called.

Program 14  (Must Read *)

int main()
{
    // Object Initialisation for the class BookClub
    BookClub mem1; // Constructor is called automatically
    BookClub mem2("Sivy");
    BookClub mem3("Rose", 4);
    BookClub *mem6;  // Constructor will not be called
    mem6 = new BookClub; // Constructor will be called
    BookClub *mem7 = new BookClub[10]; // 10 objects will be created
    BookClub *mem8 = new BookClub("Alehka", 10);

    char name[30] = "Shirley";
    int id = 5;
    mem1.enroll(name);
    mem1.deposit(id);
    cout << mem1.list();

    return 0;
}

When you want to create 10 objects, we can do in the following way. 

BookClub *mem7 = new BookClub[10]; // 10 objects will be created

The following would happen if we 'new' for (non-pointer) objects.

Program 15

int main()
{
    // Object Initialisation for the class BookClub
    BookClub mem1; // Constructor is called automatically
    BookClub mem9 = new BookClub;

    char name[30] = "Shirley";
    int id = 5;
    mem1.enroll(name);
    mem1.deposit(id);
    cout << mem1.list();

    return 0;
}
error: conversion from 'BookClub*' to non-scalar type 'BookClub' requested

Click here to learn about constructor initialiser list. 

Destructors 

Destructor is called automatically when the class instance is destroyed. It is used to clean-up the memory. 

It allows the programmer to write the necessary code to free the resources acquired by the object prior to deleting the object itself.

Syntax

myclass *myobject = new myclass(); 
delete(myobject);

The above is the appropriate way of removing the object. 

Program 16

int main()
{
    // Object Initialisation for the class BookClub
    BookClub mem1; // Constructor is called automatically
    BookClub *mem6;  // Constructor will not be called
    mem6 = new BookClub; // Constructor will be called
    
    /* Do something */
    delete mem6;   >>>>>>>>>>>>>>>>>>>>

    char name[30] = "Shirley";
    int id = 5;
    mem1.enroll(name);
    mem1.deposit(id);
    cout << mem1.list();

    return 0;
}

Here, mem6's memory will be removed by delete as well as destructor. 

The below is destructor syntax, but calling destructor is not needed. 

Method 1:

class BookClub {
   public:
       BookClub(); // Default Constructor 
       BookClub(char name[]); 
       BookClub(char name[], int id);
       void enroll(char name[]);
       void deposit(int id);
       char* list();
       ~BookClub();  >>>>>>>>>>>>>>>>>>>>
   private:
       char members_ [30]; 
       int  memberID_;
};

Method 2:  (Must Read *)


int
main() { // Object Initialisation for the class BookClub BookClub mem1; // Constructor is called automatically char name[30] = "Shirley"; int id = 5; mem1.enroll(name); mem1.deposit(id); cout << mem1.list(); mem1.~BookClub(); >>>>>>>>>>>>>> return 0; }

Calling this will make double free as the memory will be freed when the object instance is deleted. 

If we need to initialise the variables of base, first call the base constructor and call the derived class constructor.

It is good practice to have the destructor as virtual, otherwise we might call the sub class's destructor. So, the right version of destructor will be called if the base is declared with virtual.  Click here to read about virtual destructor.

Note: The most important reason why free() should not be used for de-allocating memory allocated using new is that, it does not call the destructor of that object while delete operator does.

const

It is an accessor and will not change the state of the object.

int getName() const {
     return name;
}

const int mx = numeric_limits<int>::max();  

Though it is a constant expression, its initialiser is a function call, it goes for run-time initialisation

Destructors are called when one of the following event occurs:

  • A local (automatic) object with block scope goes out of scope.
  • An object allocated using the new operator is explicitly deallocated using delete(). 
  • The lifetime of a temporary object ends.

= delete 

  • Prevents defaults from being used. 
  • If you forget to delete the default constructor, the delete keyword ensures that compiler doesn't provide by default.

class BookClub {
   public:
       BookClub() = delete; // Default Constructor 
       BookClub(char name[]); 
       BookClub(char name[], int id);
       void enroll(char name[]);
       void deposit(int id);
       char* list();
       ~BookClub();
   private:
       char members_ [30]; 
       int  memberID_;
};

Final

In C++11, similar to Java, a method that is declared final in the super class cannot be overridden; also, a method can be declared override to make the compiler check that it overrides a method in the base class.

A method which is declared final in the superclass can't be overridden. If happens, it will throw the below error. 

In file included from Army.cpp:1:

Army.h:86:10: error: virtual function ‘virtual void ArmyHospital::transfer(int, string)’ overriding final function

   86 |     void transfer(int x, string name) override;

Army.h:41:15: note: overridden function is ‘virtual void Army::transfer(int, string)

   41 |  virtual void transfer(int x, string y) final;

      |                        ^~~~~~~~~                                    


If the function is declared as virtual final, no need to redeclare them in the derived class. 



Why constructor is not virtual? (Must Read*)


Virtual functions basically provide polymorphic behavior. That is, when you work with an object whose dynamic type is different than the static (compile time) type with which it is referred to, it provides behavior that is appropriate for the actual type of object instead of the static type of the object.

Now try to apply that sort of behavior to a constructor. When you construct an object the static type is always the same as the actual object type since:

To construct an object, a constructor needs the exact type of the object it is to create [...] Furthermore [...]you cannot have a pointer to a constructor


References 

Virtual Constructor 



Comments