...
/Classes: Static Members and Best Practices
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::countwill display3since there are three objects created in memory. The subsequent output will display2because the inner scope is terminated, causing the destruction oftimer3. Finally, the output will display1because the block has also ended, resulting in the destruction oftimer2, leaving onlytimer1in 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
staticinttypecountmember variable is now declared in theprivatesection of the code.Line 16–19: A
staticgetCount()function is defined that can access theprivatestaticcountvariable and returns it.Line 29: The
countvalue is printed using thegetCount()function, which is accessed from outside the class using theTimerclass name followed by the (::) resolution operator, creatingTimer::getCount(), which is printed using thecoutstatement.
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
thispointer 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
thispointer. Notice that we’ve not written default parameters here (they are already defined at the time of declaration).Lines 13–32: A
voidtypeclockTick()function is defined. Thethispointer 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
voidtypedisplayTimer()function is defined. It accesses the member variables using thethispointer and prints them. Notice that it is aconstfunction because it never changes any values inside the function.Lines 40–49: A
voidtypeanimate()function is defined and runs an infinitewhileloop. 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 theconstkeyword 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
timerobject for theTimerclass 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();
};