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.
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.
#ifndef __ROLLINGDIE_H__ #define __ROLLINGDIE_H__ class RollingDie { private: static bool initialized; public: RollingDie(); RollingDie(int sides_count); ~RollingDie(); int roll(); int sides_count; }; #endif
#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++