C# week November Black Friday
Black friday is here! Get up to 80 % extra points for free! More info
Only this week up to 80 % off on C# courses. More info

Lesson 3 - A RollingDie in C++ and Constructors

In the previous lesson, First OOP application in C++ - Hello object world, we programmed our first object-oriented application in C++. We are already able to create new classes with fields and parameterized methods with return values. Today, we're going to start working on an arena in which two warriors will battle against each other. The battle will be turn-based and one warrior will decrease the health points of other one based on his attack and on the other warrior's defense. Essentially, we'll be simulating a board game, so we'll add a rolling "die" to our game so as to add a bit of randomness. We're also going to learn to declare custom constructors.

Creating a project

We'll start by creating a new empty application and naming it Arena. We'll create a main.cpp file as usual with the basic code:

#include <iostream>

using namespace std;

int main()
{
    cin.get();
    return 0;
}

We'll also add a new class to our project and name it RollingDie. Let's think about the fields we'll have to add to our die in order for it to function according to our current needs. We should be able to choose the number of sides. Typically 6 or 10 sides as is standard in this type of game. Our die will a single field now: sides_count of the int type. Our class now looks like this:

We've removed the generated #pragma once, but kept the generated methods.

RollingDie.h

#ifndef __ROLLINGDIE_H__
#define __ROLLINGDIE_H__

class RollingDie
{
public:
    RollingDie();
    ~RollingDie();
    int sides_count;
};

#endif

–°onstructors

Until now, we were unable to set class fields when creating a new instance. We had to write something like this:

RollingDie die;
die.sides_count = 6;

However, we would like to set the number of sides while the instance is being created. We've already discussed constructors a bit. A constructor is a method that is called while an object instance is being created. We use it to set the internal state of the object and perform any necessary initializations. A simple constructor has already been generated by Visual Studio. It's written as: RollingDie(). It has a blank body ({}) in the RollingDie.cpp file. However, we'll now add something to its body. In the constructor, we'll set the number of sides to a fixed value. The constructor will look like this:

RollingDie.cpp

RollingDie::RollingDie()
{
    sides_count = 6;
}

We declare the constructor as a method, but it doesn't have a return type. Also, the constructor must have the same name as the class name, which in our case is RollingDie.

If we create the die now, it'll have the sides_count field set to 6.

main.cpp

We'll print the number of sides to the console, so as to visually confirm that the value is there. Let's move on to main.cpp so we could create our first die and print the number of its sides to the console:

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

using namespace std;

int main()
{
    RollingDie die;
    cout << die.sides_count << endl;
    cin.get();
    return 0;
}

The output:

Console application
6

Now we have visual confirmation that the constructor had been called. However, we'd like to be able to specify how many sides our die will have.

RollingDie.cpp

Let's add a parameter to our constructor (we have to modify the declaration in RollingDie.h too to accept an int parameter):

RollingDie::RollingDie(int _sides_count)
{
    sides_count = _sides_count;
}

Notice that we prefixed the parameter name with _, otherwise it'd have the same name as the field and cause an error. Let's go back to main.cpp and pass a value for this parameter in the constructor:

RollingDie die(10); // a constructor with a par. 10 is called
cout << die.sides_count << endl;

The output:

Console application
10

Note the use of parentheses. If we omit the parentheses (as we have done so far), then the parameterless constructor (the one that was originally generated) is called. If a class has only constructors that accept parameters, then we must create an instance that way. If the class had a parameterless constructor and some other constructors, then the one without parameters would be called when creating an instance without parentheses. The only difference is when creating an instance dynamically where parentheses may be, but don't have to.

RollingDie sixsided;
RollingDie sixsided2();  // won't work
RollingDie tensided(10);
RollingDie* dynamic_sixsided = new RollingDie;
RollingDie* dynamic_sixsided2 = new RollingDie(); // will work
RollingDie* dynamic_tensided = new RollingDie(10);

Note: If no constructor is defined (as it was in the previous lesson), then the compiler will generate a parameterless empty constructor automatically.

In the constructor, we should ask for all the information the class needs to work properly. This will force programmers using our class to pass these parameters (otherwise they won't be able to create the instance).

However, we assume a 6-sided die is the default, so we'd like to create a six-sided dice automatically if we don't specify the parameter. There are two ways to do this - the first is to use default parameter values:

// RollingDie.h
class RollingDie
{
public:
    RollingDie(int _sides_count);
    ~RollingDie();
    int sides_count;
};

// RollingDie.h
RollingDie::RollingDie(int _sides_count = 6)
{
    sides_count = _sides_count;
}

// main.cpp
RollingDie sixsided;
RollingDie tensided(10); // a constructor with a par. 10 is called
cout << sixsided.sides_count << endl;
cout << tensided.sides_count << endl;

The output:

Console application
6
10

Another approach would be to overload the constructor by declaring another parameterless one:

// RollingDie.h
class RollingDie
{
public:

    RollingDie(int _sides_count);
    ~RollingDie();
    int sides_count;
};

// RollingDie.cpp
RollingDie::RollingDie()
{
    sides_count = 6;
}

RollingDie::RollingDie(int _sides_count)
{
    sides_count = _sides_count;
}

RollingDie::~RollingDie()
{
}

Such an implementation isn't the best idea, but we'll talk more about it later.

C++ doesn't mind us having two methods with the same name as long as their parameters are different. We say that the RollingDie() method, which in this case is the constructor, is overloaded. This works for all methods with same names and different parameters, not just constructors. Many C++ methods have several overloads, try to look at the getline() method on the cin object. Visual Studio offers overloaded versions of methods when we're typing them. We can scroll through the method variants with the arrow keys. This "scroll-tool" is called IntelliSense. It's good to look through method overloads, so you don't end up programming something what has already been done for you.

IntelliSense hints for overloaded methods in C++

Invoking Constructors

In the previous case, we set the value of 6 to the sides_count attribute directly in the parameterless constructor. Let's consider we have 5 attributes like this one and 3 constructors. Setting up all the attributes in all constructors isn't very wise since we'd repeatedly do the same thing and we could make a mistake. As a rule, a more specific constructor (with fewer parameters) calls the more general one (the one with more parameters). But how do we call a constructor manually, knowing just it's called automatically when an instance is being created? For this case, C++ introduces delegating constructors. The syntax is to add a colon after the constructor declaration and then the name of the constructor we want to be called with its parameters follows. An example will serve the best:

RollingDie.cpp

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

using namespace std;

RollingDie::RollingDie() : RollingDie(6)
{
    cout << "Parameterless constructor called" << endl;
}

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

RollingDie::~RollingDie()
{
}

And the usage:

RollingDie sixsided;

In the sample we see that both constructors have been called:

Console application
Parametric constructor called
Parameterless constructor called

Note: Delegating constructors are supported since C++ 11, so it may not work for all compilers. Delegating constructors like this is useful for one more thing. Let's consider we want to create a user instance for which we need a name. That's not a problem yet. But we'll need to declare it in another object, in the arena, when we don't yet know the name. Sample classes for players and arena follow:

Player.h

#ifndef __PLAYER__H_
#define __PLAYER__H_
#include <string>
using namespace std;

class Player
{
public:
    string name;
    Player(string _name);
};
#endif

Player.cpp

#include "Player.h"

Player::Player(string _name)
{
    name = _name;
}

Arena.h

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

class Arena
{
public:
    Player first;
    Player second;
    Arena();
};
#endif

Arena.cpp

#include "Arena.h"

Arena::Arena()
{
}

The project should not compile and report the following message (for Visual Studio):

error C2512: 'Player': no appropriate default constructor available

The reason is simple. The constructor takes care of creating an instance. This means that when the constructor finishes, the class must be ready for use. However, this doesn't work for us because the player needs a name. We could try the following code:

Arena::Arena()
{
    first = Player("Carl");
    second = Player("Paul");
}

But we still get the same error. Before any method (including the constructor) is called on an instance, the instance must already be created correctly. So we have to tell C++ somehow how to create a player before the Arena itself is created. The syntax is the same as for the delegating constructor, using the variable name instead of the class name:

#include "Arena.h"

Arena::Arena(string name_first, string name_second) : first(name_first), second(name_second)
{

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

class Arena
{
public:
    Player first;
    Player second;
    Arena(string name_first, string name_second);
};
#endif
#ifndef __PLAYER__H_
#define __PLAYER__H_
#include <string>
using namespace std;

class Player
{
public:
    string name;
    Player(string _name);
};
#endif
#include "Player.h"

Player::Player(string _name)
{
    name = _name;
}
#include <iostream>
#include "Arena.h"

using namespace std;

int main()
{
    Arena arena("Carl", "Paul");
    cout << arena.first.name << " and " << arena.second.name << " fight" << endl;
    cin.get();
    return 0;
}
Console application
Carl and Paul fight

That would be all for today's lesson. In the next lesson, Introducing destructors and more about constructors in C++, we'll talk about the main reason for the existence of constructors, describe the destructors, and finish our rolling die.


 

Download

Downloaded 0x (455.76 kB)
Application includes source codes in language C++

 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
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 College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Previous article
First OOP application in C++ - Hello object world
All articles in this section
Object-oriented programming in C++
Thumbnail
Next article
Introducing destructors and more about constructors in C++
Activities (3)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!