Get up to 80 % extra points for free! More info:

Lesson 4 - Introducing destructors and more about constructors in C++

In the previous lesson, A RollingDie in C++ and Constructors, we described the constructor syntax, including advanced constructs such as the delegating constructor and invoking constructors for attributes. Today, we're going to describe destructors and show the main purpose for which constructors and destructors are used.

Destructors

Like the constructor that is called immediately after the instance is created, the destructor is called automatically before the instance is deleted. The instance is usually deleted at the end of the block (that is, the end of the function or the closing brace }). We write the destructor as a method that starts with a tilda (~) followed by the class name. The destructor never has parameters and does not return a value. Such a basic destructor has already been generated by Visual Studio for us and is empty (if we don't provide a custom destructor, the compiler will create a destructor with an empty body automatically):

RollingDie.h

class RollingDie
{
public:
    RollingDie();
    RollingDie(int _sides_count);
    ~RollingDie();  // destructor declaration
    int sides_count;
};

RollingDie.cpp

RollingDie::~RollingDie()  // empty destructor
{
}

To see when the destructor is called, we'll print to the console in it:

#include <iostream>  // if missing
using namespace std;  // if missing
RollingDie::~RollingDie()
{
    cout << "Calling the destructor for the die with " << sides_count << " sides" << endl;
}

In main.cpp, we'll put the following code, which shows the cases when the destructor is called.

void function(RollingDie die)
{
    cout << "Function" << endl;
}

int main()
{
    RollingDie first(1);
    if (true)
    {
        RollingDie second(2);
        function(second);
        cout << "Function finished" << endl;
    }
    // cin.get();
    return 0;
}

We see the logs in the application output:

Console application
Parametric constructor called
Parametric constructor called
Function
Calling the destructor for the die with 2 sides
Function finished
Calling the destructor for the die with 1 sides

When we analyze the example, we find that the destructor is called before the closing curly braces and at the end of the function. That's when the variable is no longer needed and C++ will remove it from memory. For a better understanding, let's add comments to the code as well:

void function(RollingDie die)
{
    cout << "Function" << endl;
} // die's destructor called

int main()
{
    RollingDie first(1); // first constructor
    if (true)
    {
        RollingDie second(2); // second constructor
        function(second);
        cout << "Function finished" << endl;
    } // second's destructor
    // cin.get(); if we keep this call, we won't see the the first die being deleted
    return 0;
} // first's destructor called

Constructors calls are also printed because we left the code from the previous lesson. You might be surprised that three destructors are called, but only two constructors. In one case, the copying constructor is called, but we'll deal with it in another lesson. For now, we just need to know when the destructor is called.

Constructors for initialization

Now let's look at one case where constructors are useful - class initialization.

Let's define a roll() method in the RollingDie that returns a random number from 1 to the number of sides. It's very simple, the method will have no parameter and the return value will be int. To get a random number, we call the rand() function from the cstdlib library.

RollingDie.h

#ifndef __ROLLINGDIE_H__
#define __ROLLINGDIE_H__

class RollingDie
{
public:
    RollingDie();
    RollingDie(int _sides_count);
    ~RollingDie();
    int roll();
    int sides_count;
};
#endif

RollingDie.cpp

#include <iostream>
#include <cstdlib>
#include "RollingDie.h"
using namespace std;
// ... already defined methods
int RollingDie::roll()
{
    return rand() % sides_count + 1;
}

rand () returns a pseudo random number. To be within the required range, we need to use % sides_count + 1. The number 1 is added to make the random numbers from one and not zero. A pseudo-random number means that it starts with some number and next numbers are calculated by some operation with the initial number. This approach has one disadvantage - put the following code into main.cpp (you can delete the original one):

#include <iostream>
#include "RollingDie.h"
#include "Arena.h"

using namespace std;


int main()
{
    RollingDie die;
    for (int i = 0; i < 10; i++)
        cout << die.roll() << " ";
    cin.get();
    return 0;
}

Note that if we run the program several times, it always generates the same numbers (even though it should generate them randomly). This is because the initial number is always the same. We need to start with a different number every time we run the program. We'll do it using the srand() method and passing the current time to it. And because it actually initializes the instance, we'll put that code into the constructor.

Note: In addition to the cstdlib library, the ctime library must also be included.

RollingDie::RollingDie(int _sides_count)
{
    cout << "Parametric constructor called" << endl;
    sides_count = _sides_count;
    srand(time(NULL));
}

Now the rolling die always generates different numbers and we are done.

Using Constructors for Memory Management

The second case where we can use the constructor (and the destructor) is memory management. Since constructors and destructors are called automatically, we are sure that the code is always executed. So we can allocate memory in the constructor and delete it in the destructor. Let's take our arena as an example, where there are currently two warriors. Let's say we want to enter the number of warriors as a parameter - so we must create an array of the warriors dynamically. Edit the Arena.h file as follows:

#ifndef __ARENA_H_
#define __ARENA_H_
#include "Player.h"

class Arena
{
public:
    Player** players;
    int players_count;
    Arena(int _players_count); // the parameter name has been changed
    ~Arena();
};
#endif

Don't be scared of the two asterisks - it's an array of pointers to Player (we cannot create only an array of players because we don't have the default = parameterless constructor which is needed for that). We allocate this array in the constructor, and we ask for the names according to the number of players. Then, in the destructor, we perform the opposite operation and delete everything. Let's see the code:

Arena.cpp

#include <iostream>
#include "Arena.h"

using namespace std;

Arena::Arena(int _players_count)
{
    players_count = _players_count; // storing the player count
    players = new Player*[players_count]; // creating an array for the players
    for (int i = 0; i < players_count; i++)
    {
        string name;
        cout << "Enter a player name: ";
        cin >> name;
        players[i] = new Player(name); // creating the player
    }
}

Arena::~Arena()
{
    for (int i = 0; i < players_count; i++)
        delete players[i]; // deleting the players
    delete[] players; // deleting the array
    players = NULL;
}

If we did not delete the memory, it'd remain allocated and we wouldn't be able to access it anyhow (there would be no pointer to it) and it would also be impossible to delete it later. For example, if we were creating instances in a loop, then the program would start to consume more and more RAM until it'd take it all (and having a gigabyte of RAM for that small application is rather strange). If there's no free RAM memory and the program asks for more memory, the operating system no longer has anything to allocate and will terminate the application. Therefore, if you find your application crashing after some time, try to check how much memory space it takes and if this space is constantly growing, you may not be freeing memory somewhere, causing a memory leak.

main.cpp

So we have finished our arena and can use it in main.cpp:

#include <iostream>
#include "RollingDie.h"
#include "Arena.h"

using namespace std;


int main()
{
    RollingDie die;
    for (int i = 0; i < 10; i++)
        cout << die.roll() << " ";
    cout << endl;

    Arena arena(4);
    cin.get();
    return 0;
}

The result:

Console application
Parametric constructor called
Parameterless constructor called
2 6 1 2 1 6 2 3 1 4
Enter a player name: Paul
Enter a player name: Carl
Enter a player name: George
Enter a player name: Lucas
Calling the destructor for the die with 6 sides

Everything works like a charm. Allocating and freeing memory is the most common thing that happens in constructors and destructors, so I suggest you take a good look at the last example to understand how it works.

That's all for this lesson. The next time, The this pointer in C++, we'll remove those nasty parameter names starting with underscores. The source code of today's lesson is attached for download below the article as always.


 

Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.

Download

By downloading the following file, you agree to the license terms

Downloaded 2x (964.38 kB)
Application includes source codes in language C++

 

Previous article
A RollingDie in C++ and Constructors
All articles in this section
Object-Oriented Programming in C++
Skip article
(not recommended)
The this pointer in C++
Article has been written for you by David Capka Hartinger
Avatar
User rating:
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn university David learned IT at the Unicorn University - a prestigious college providing education on IT and economics.
Activities