Classes: Static Members and Best Practices
Learn about static members and their best programming practices.
In this lesson, we’ll learn two important things:
The concept of static members in classes that provide class-level functionality and shared data among instances.
The practice of separating the declaration and definition of a class into different files. This coding practice offers several benefits, including enhanced modularity, reusability, and maintainability. By adopting this approach, we can create more organized and scalable classes that are easier to work with and improve overall compilation times.
Static member variables and member functions
Static data members are another essential aspect that gives the OOP the versatility of class manipulation. These special members are in C++ and are associated with the class rather than individual objects. Before we jump to the example, let’s see what the static
members are.
Static member variable
In C++, static member variables are class variables that are shared among all objects of the class. They are declared using the static
keyword within the class definition and must be defined and initialized outside the class scope. Static member variables provide a way to maintain and share common data among class instances. A static variable is not the property of any object but actually the property of the class. It’s stored in the global/static section of the memory. It keeps residing there until the end of the program. The static attribute is accessible anywhere in the scope of the class.
Let’s run an example to see how this works:
#include <iostream>using namespace std;class Timer{public:static int count; // Declaration of static member variable// Default constructorTimer(){count++; // Increment count when a new object is created}~Timer(){count--; // Increment count when a new object is created}};int Timer::count = 0; // Definition and initialization of static member variableint main(){Timer timer1;{Timer timer2;{Timer timer3;cout << "Count: " << Timer::count << endl;// Count should be 3}cout << "Count: " << Timer::count << endl;// Count should be 2}cout << "Count: " << Timer::count << endl;// Count should be 1 nowreturn 0;}
In the example above, the Timer
class has a count
static member variable, which is incremented when a new object is created and decremented when an object is destroyed. By using static member variables, we can keep track of the number of active objects for the class. Within the main()
function, we create multiple instances of the Timer
class, and at each stage, we display the value of count
. As the objects are created and destroyed, count
is updated accordingly.
Understanding static member variables allows us to manage and monitor the state of objects within a class, providing valuable insights and control over their behavior.
Tip: Execute the code above and observe the output. The first
Timer::count
will display3
since there are three objects created in memory. The subsequent output will display2
because the inner scope is terminated, causing the destruction oftimer3
. Finally, the output will display1
because the block has also ended, resulting in the destruction oftimer2
, leaving onlytimer1
in the memory.
Static member functions
Static member functions are functions that are associated with classes rather than objects. They can be called without creating an object and are also declared using the static
keyword. These static functions can only access static member variables in C++. Other than that, they are declared and defined similarly to normal member functions. To call these functions using only the class name, we use the ::
(resolution) operator. A general syntax is given below:
ClassName::functionName();
To understand how static members and functions are used, we can modify the example above as follows:
#include <iostream>using namespace std;class Timer{private:static int count; // Declaration of static member variablepublic:// Default constructorTimer(){count++; // Increment count when a new object is created}static int getCount(){return count; // Static member function to retrieve the count value}};int Timer::count = 0; // Definition and initialization of static member variableint main() {Timer timer1;Timer timer2;Timer timer3;cout << "Count: " << Timer::getCount() << endl; // Accessing static data member using the class namereturn 0;}
The code remains the same except for the following added lines:
Line 7: The
static
int
typecount
member variable is now declared in theprivate
section of the code.Line 16–19: A
static
getCount()
function is defined that can access theprivate
static
count
variable and returns it.Line 29: The
count
value is printed using thegetCount()
function, which is accessed from outside the class using theTimer
class name followed by the (::
) resolution operator, creatingTimer::getCount()
, which is printed using thecout
statement.
After executing the provided example, we can observe that the static member variable count
can be accessed, resulting in an output of Count: 3
. This demonstrates the utility of static member variables, as they can be shared among all objects of the class and used to perform operations or retrieve shared data without the need for object-specific functions. Furthermore, the static member functions complement the static data members by providing class-level functionality.
Note: The static member functions do not have access to any object's data members, as no
this
pointer is passed to them. This restriction ensures that static functions operate solely on the class-level and do not rely on specific object instances. Overall, the use of static data members and functions enhances the encapsulation and abstraction of data within the class.
Best coding practices
So far, we’ve learned how to make and manipulate classes. However, we excluded an essential coding aspect: best practices. Our codes above lack modularity and are generally difficult to understand due to the complex nature of member functions and divisions in classes. As the complexity increases, we lose the notion of this procedural to OOP journey, which was to promote modularity and ease for the users and learners by providing abstraction. So, next, we’ll discuss how to follow best practices while coding with classes. We’ll make a timer that will display the time and will be incremented every second.
File distinction
While writing code for classes, we divide our code into three categories:
The header file (.h
or .hpp
)
The header file contains the class declaration, along with its member functions, variables, and overall structure. It serves as an interface to the class and is included in all other source files where the class declaration (and its inner details and information) are needed. It provides the structure of the class without showing the internal implementation details. In our example, we’ll make a header file named Timer.h
as shown below:
// Timer.hclass Timer{private:int sec, min, hour;public:Timer(int=0, int=0, int=0);void clockTick(); // This cannot be 'const'void displayTimer()const;void animate(); // This cannot be 'const'};
We’ve defined our member variables as private
and member functions as public
. This is the overall structure of the class, where we have three variables—sec
, min
, and hour
—to store timer data and four member functions, where Timer(int=0, int=0, int=0);
is the default constructor with the default parameters declaration. The clockTick()
function, as we’ll see in its implementation, increments the timer by one second. The displayTimer()const
function prints the timer value, and the animate()
function calls the clockTick()
function after a one-second delay, making the clock run in real-time.
Note: A header file that includes the class declaration, private data members, and public members is referred to as a class interface or class declaration. It serves the purpose of defining the ADT represented by the class and specifies the public interface for interacting with the class. This approach of creating a header file is commonly known as defining an ADT.
The source file (.cpp
)
The source file contains the class definition declared in the header file. It entails all the implementation details of the member functions. This source file includes the header file to allow access to the class declarations. The general syntax to include the header file is:
#include "headerFile.h"
The code for our timer implementation is given below.
#include "Timer.h" // Header file#include<iostream>#include<iomanip>#include<unistd.h>using namespace std;Timer::Timer(int h, int m, int s){this->sec = s, this->min = m, this->hour = h;// Here, the sec variable represents seconds, the min variable represents minutes, and the hour variable represents hours}void Timer::clockTick(){// increment secthis->sec++;// if sec reaches 60if (this->sec == 60){// increment minthis->min++;// if min reaches 60if (this->min == 60){// increment hourthis->hour++;this->min = 0;}this->sec = 0;}}void Timer::displayTimer() const{cout << "Time: " << this->hour << " hours | " << this->min<< " minutes | " << sec << " seconds" << endl;}void Timer::animate(){while (true){sleep(1); // sleep system calls to sleep for 1 secondsystem("clear"); // clears the consoleclockTick(); //increments the timerdisplayTimer(); // displays the timer}}
Line 1: The header file is included in the source file.
Lines 8–11: The default constructor with default parameters is defined and updates the member variables using the
this
pointer. Notice that we’ve not written default parameters here (they are already defined at the time of declaration).Lines 13–32: A
void
typeclockTick()
function is defined. Thethis
pointer is used to access and update its member variables. It increments the timer by one second when it’s called. Notice that thisclockTick()
function is notconst
.Lines 34–38: A
void
typedisplayTimer()
function is defined. It accesses the member variables using thethis
pointer and prints them. Notice that it is aconst
function because it never changes any values inside the function.Lines 40–49: A
void
typeanimate()
function is defined and runs an infinitewhile
loop. This function uses thesleep(1)
function to halt the system for one second, clears the console using thesystem("clear")
command, increments the timer by one second usingclockTick()
, and prints the updated value using thedisplayTimer()
function, thus giving it an animated appearance as if a real digital clock is ticking. Notice thatanimate()
is not a constant (const
) function because it is calling a nonconstant function inside its body (theclockTick()
function). If we add theconst
keyword in the declaration of theanimate()
function, we can’t callclockTick()
from this function.
Remember: Constant (
const
) member functions can be called by both constant and nonconstant objects, while nonconstant member functions can only be called by nonconstant objects.This means that constant (
const
) member functions can’t call nonconstant member functions directly, but nonconstant member functions can call constant (const
) member functions.
The main program file (.cpp
)
The main program file is the int main()
file where we execute our program. It should include the header file for the class access; here, we will make the class object and perform operations. The main idea behind this separation of files is obviously to promote modularity, but it also serves another role. A separate header file for a class enables that class to be used in different cpp files. For example, if multiple users want access to the Timer
class, they only need to add the #include "Timer.h"
to access its complete functionality instead of rewriting the whole class implementation in their code. Let’s see what our main program file would look like.
#include "Timer.h"#include<iostream>using namespace std;int main(){Timer timer(1, 30, 0); // Set initial time to 1h 30m 0s//timer.displayTimer(); // Display the timertimer.animate(); // Start the timer (increments every second)return 0;}
Line 1: The header file is included in the source file.
Line 7: A
timer
object for theTimer
class is made, and it is initialized to1h 30m 0s
.Line 8: The
displayTimer()
function is called and displays the timer’s initial value.Line 9: The
animate()
function is called that prints the running timer.
A complete working example of the above code is given below.
class Timer { private: int sec, min, hour; public: Timer(int=0, int=0, int=0); void clockTick(); void displayTimer()const; void animate(); };