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:

Press + to interact
#include <iostream>
using namespace std;
class Timer
{
public:
static int count; // Declaration of static member variable
// Default constructor
Timer()
{
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 variable
int 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 now
return 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.

Press + to interact
canvasAnimation-image
1 of 12

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 display 3 since there are three objects created in memory. The subsequent output will display 2 because the inner scope is terminated, causing the destruction of timer3. Finally, the output will display 1 because the block has also ended, resulting in the destruction of timer2, leaving only timer1 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:

Press + to interact
#include <iostream>
using namespace std;
class Timer
{
private:
static int count; // Declaration of static member variable
public:
// Default constructor
Timer()
{
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 variable
int main() {
Timer timer1;
Timer timer2;
Timer timer3;
cout << "Count: " << Timer::getCount() << endl; // Accessing static data member using the class name
return 0;
}

The code remains the same except for the following added lines:

  • Line 7: The static int type count member variable is now declared in the private section of the code.

  • Line 16–19: A static getCount() function is defined that can access the private static count variable and returns it.

  • Line 29: The count value is printed using the getCount() function, which is accessed from outside the class using the Timer class name followed by the (::) resolution operator, creating Timer::getCount(), which is printed using the cout 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:

Press + to interact
// Timer.h
class 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.

Press + to interact
#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 sec
this->sec++;
// if sec reaches 60
if (this->sec == 60)
{
// increment min
this->min++;
// if min reaches 60
if (this->min == 60)
{
// increment hour
this->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 second
system("clear"); // clears the console
clockTick(); //increments the timer
displayTimer(); // 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 type clockTick() function is defined. The this pointer is used to access and update its member variables. It increments the timer by one second when it’s called. Notice that this clockTick() function is not const.

  • Lines 34–38: A void type displayTimer() function is defined. It accesses the member variables using the this pointer and prints them. Notice that it is a const function because it never changes any values inside the function.

  • Lines 40–49: A void type animate() function is defined and runs an infinite while loop. This function uses the sleep(1) function to halt the system for one second, clears the console using the system("clear") command, increments the timer by one second using clockTick(), and prints the updated value using the displayTimer() function, thus giving it an animated appearance as if a real digital clock is ticking. Notice that animate() is not a constant (const) function because it is calling a nonconstant function inside its body (the clockTick() function). If we add the const keyword in the declaration of the animate() function, we can’t call clockTick() 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.

Press + to interact
#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 timer
timer.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 the Timer class is made, and it is initialized to 1h 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();
};

The complete Timer code