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

Lesson 9 - Static Class Members in C++

In the previous lesson, Constant methods in C++, we learned about constant methods in C++. In today's C++ tutorial, we're going to go over static class members. Until now, we only used data, states, being carried by an instance. Fields, which we've defined, belonged to an instance and were unique to each instance (every warrior has his own health). OOP, however, allows us to declare fields and methods on a class itself. We call these members static, sometimes called class members, who are independent of an instance.

Beware of static members - Object-Oriented Programming in C++WARNING! Today's lesson will show you that static members are approaches that actually violate the object-oriented principles. OOP includes them only for special cases and in general everything can be written without static members. We always have to think carefully before adding static members. Generally, I would recommend for you not to use static members ever, unless you're absolutely sure what you're doing. Like global variables, static members are something that enable you to write bad code and violate good practices. Today, we'll go over this topic just to make you understand some static methods and classes, not to write your own. Use this knowledge wisely, there will be less evil in the world.

Static (class) fields

We can declare various members as static, let's start with fields. As I mentioned in the introduction, static members belong to a class, not to an instance. So the data stored there can be read even if an instance of the class has not been created. Basically, we could say that static fields are shared among all class instances, but even that wouldn't be accurate since they aren't actually related to instances.

For example, we'd require that the name length must be at least 4 characters. This constant (4) should not be inserted directly in the code, but we should keep it somewhere (since we might need to change it later). We'll store it as a static field. The syntax is the same as a regular fields, we just add the static keyword before it:

Player.h

class Player
{
private:
    Warrior warrior;
    string name;
public:
    static int MIN_NAME_LENGTH;
    Player(string name, RollingDie &die);
    string getName();
    Warrior& getWarrior();
};

Player.cpp

int Player::MIN_NAME_LENGTH = 4;

Player::Player(string name, RollingDie &die) : warrior(100, 8, 5, die)
{
    if (name.length() < MIN_NAME_LENGTH)
        exit(1);
    this->name = name;
}

Notice how the declaration and initialization are separated. The declaration is in the Player.h file and contains the static keyword. The initialization must be in the *.cpp file and no longer uses static. Now we have a static field but we are able to change it. Because it's a constant, we should declare it as a constant. For constants, both the declaration and initialization must be in the *.h file (we'll delete the part in Player.cpp).

Player.h

class Player
{
private:
    Warrior warrior;
    string name;
public:
    static const int MIN_NAME_LENGTH = 4;
    Player(string name, RollingDie &die);
    string getName();
    Warrior& getWarrior();
};

Because it's a usual cause of errors, let's repeat: constant static fields must be initialized in *.h files, while only static fields must be initialized in *.cpp files.

The static field name alone (as with regular fields) can only be used in class methods (MIN_NAME_LENGTH). For example, if we wanted to use the constant in the main() function, we'd have to specify the class name too (Player::MIN_NAME_LENGTH).

Note: We could use the same syntax for using the contant within the Player class too, it's just up to the programmer.

cout << "Min name length: " << Player::MIN_NAME_LENGTH << endl;

Static methods

Static methods are called on the class. All they usually are is utilities that we need to use often and creating an instance every time would be counterproductive.

Let's show a real example again. When creating the arena, we ask for names in the constructor. This is a bad approach - the constructor should only serve to create an instance. Any other logic (such as obtaining inputs) shouldn't be there. I must point this out because I have seen this rule being violated several times. For example, the constructor itself triggered some algorithm the class represented. This is completely wrong in terms of OOP and good practices. The constructor should only create an instance, nothing more. How do we fix our Arena? First, we'll change the constructor so it only accepts the array of names and their number. And, of course, we'll provide a static method that asks for the names and creates the arena for us.

First, let's edit the Arena.h header file. We must change the constructor parameters and add the declaration of the static method.

Arena.h

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

class Arena
{
private:
    Player** players;
    int players_count;
    int round;
    bool winnerExists();
    int countAlive();
public:
    Arena(string* names, int players_count, RollingDie &die); // edited
    ~Arena();
    void print();
    void printWinner();
    void fight();
    static Arena* createArena(int players_count, RollingDie &die); // static method
};
#endif

Now we need to change the implementation of the constructor and implement our static method:

Arena.cpp

// ...more implementation

Arena::Arena(string* names, int players_count, RollingDie &die) : round(1)
{
    this->players_count = players_count;
    this->players = new Player*[players_count];
    for (int i = 0; i < players_count; i++)
        this->players[i] = new Player(names[i], die);

}

Arena* Arena::createArena(int players_count, RollingDie &die)
{
    string* names = new string[players_count]; // create an array for names
    for (int i = 0; i < players_count; i++)
    {
        cout << "Enter a player name: ";
        cin >> names[i];
    }
    Arena* arena = new Arena(names, players_count, die);
    delete[] names; // must not forget to delete the array as well
    return arena;
}

And that's all :) All we have to do is edit the main.cpp file to use our new static method:

main.cpp

#include <iostream>
#include "RollingDie.h"
#include "Arena.h"
using namespace std;


int main()
{
    RollingDie die;
    Arena* arena = Arena::createArena(3, die); // call static method
    arena->fight();
    arena->print();
    arena->printWinner();
    cin.get(); cin.get();
    return 0;
}

As with the static field, we access the method using the class name, two colons and the method name.

Fixing the RollingDie

Now that we can work with static fields, we can fix our RollingDie class. That we have nothing to fix? Try running the following code:

int main()
{
    RollingDie die1;
    for (int i = 0; i < 10; i++)
        cout << die1.roll() << " ";
    cout << endl;
    RollingDie die2;
    for (int i = 0; i < 10; i++)
        cout << die2.roll() << " ";
    cout << endl;
    return 0;
}

You'll see that both dice rolled the same values. How is it possible? In the die's constructor, we changed the initial seed for the random number generator (see the C++ constructors lessons). But the value of the time() function we use for initialization changes only once per second. Therefore, when the second die is created, the constructor is called again and the generator's initial value is set to the same value as for the first die. How to get out of it? All we need to do is to initialize the generator only once. So we'll create a (private) static field that will tell us whether we already did the initialization. Based on this variable, we'll only initialize once.

class RollingDie
{
private:
    static bool initialized;
public:
    RollingDie();
    RollingDie(int sides_count);
    ~RollingDie();
    int roll();
    int sides_count;
};
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "RollingDie.h"

using namespace std;

bool RollingDie::initialized = false;

RollingDie::RollingDie() : RollingDie(6)
{
}

RollingDie::RollingDie(int sides_count)
{
    this->sides_count = sides_count;
    if (!RollingDie::initialized) // or if (!initialized)
    {
        srand(time(NULL));
        initialized = true;
    }
}

RollingDie::~RollingDie()
{
    cout << "Calling the destructor for the die with " << sides_count << " sides" << endl;
}

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


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

    cin.get();
    return 0;
}

Now the program is working properly. So we have static members finished and next time, Overloading operators in C++, we'll look at overloading operators, so you have something to look forward to.


 

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 8x (1.03 MB)
Application includes source codes in language C++

 

Previous article
Constant methods in C++
All articles in this section
Object-Oriented Programming in C++
Skip article
(not recommended)
Overloading operators 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