In the previous lesson, we learned the theory behind OOP and how classes are used to implement various OOP concepts like abstraction and encapsulation. In this lesson, we’ll explore the coding aspect of this concept. We’ll learn how to implement a class, its syntax, and its various use cases in complex coding examples.

Definition and syntax of a class

As mentioned before, a class represents an ADT with attributes and functionality. It has two parts: public and private (limited to our scope). Using this division, we can hide sensitive data and limit the user’s access to its private members, allowing a desirable response from the class and promoting the notion of code extendibility. Let’s get started.

In C++ programming, we use the class keyword before the user-defined class name to declare a class.

class ClassName;
Class declaration

We define two major components in that class: member attributes and member functions. A general syntax to define a class is given below:

class ClassName
{
// Member variables
dataType member1;
...
// Member functions
funcType function();
...
}
Defining a class

Member attributes are the variables declared to store the relevant data of the class’s object. In contrast, member functions are the functions defined within the class that operate on the class’s member attributes/variables. ClassName is the ADT or a class that the user creates. Let’s work on an actual example and see what it looks like when we implement a class named Timer.

class Timer
{
// Member attributes
int sec, min, hour;
// Member functions
void clockTick();
void displayTimer();
};
Example class

This class has three int type member variables—sec, min and hours—and two void type member functions—clockTick() and displayTimer(). It is important to note that in this class, we have declared our functions but not defined them. So, how and where do we define them?

Declaration and definition of member functions

There are two ways we can define a class member function. Although both methods work the same in terms of implementation, they are used in different use cases, mainly depending on the function’s code complexity and length. Let’s go through them individually.

Inline member function definitions

Inline member functions are member functions that are defined within the class scope. The general syntax is given below:

class ClassName
{
void memberFunction()
{
// Function body
}
};
Syntax of a class

We always want our code to be modular, clean, and easier to understand. We don't want our declared class to be crowded by complex code. Therefore, this method is used if the function to be written is small and simple. In our example of the Timer class, the code would look like this:

class Timer
{
// Member attributes
int sec, min, hour;
// Member functions
void clockTick()
{
// Function body
}
void displayTimer()
{
// Function body
}
};
Code for the Timer class

Member function definitions outside of class

On the flip side, if the content of a member function is lengthy and complex, we prefer to define our function outside its class declaration using the following format:

class ClassName
{
void memberFunction(); // prototype of the function
};
void ClassName::memberFunction() // declaration of the function
{
// Function body
}
Defining the function outside its class declaration

To access/define a member function outside the class, we use the :: (resolution) operator after the class name. In our example of the Timer class, the code would look like this:

class Timer
{
// Member attributes
int sec, min, hour;
// Member functions declaration
void clockTick();
void displayTimer();
};
// Member function definition
void Timer::clockTick()
{
//Function body
}
int Timer::displayTimer()
{
//Function body
}
Example class and function declaration

Therefore, the class only holds the declaration of the member attributes and functions prototypes while they are defined outside the declaration of the class, making the class appear much cleaner and simpler.

How to make an object of a class

To make an object of a class, we follow the same rule that we followed when making a structure variable. An object is a variable to the ADT we define as a class. A general syntax to form an object is shown below.

ClassName objectName;
General syntax

Similar to any data type variable, to access the public members of the object, we use the . (dot) operator.

objectName.memberFunction();
Accessing the public members

Above is an example of calling the member function of an object. Likewise, if there are any member attributes/variables that are publically accessible, we can modify them as follows:

objectName.memberVariable = value;
Modification of publically accessible variables

Execute the following program, and you’ll see a surprise.

Press + to interact
#include <iostream>
using namespace std;
class Timer
{
// Member attributes
int sec, min, hour;
// Member functions
void clockTick();
void displayTimer();
};
void Timer::displayTimer()
{
cout << "Time: " << hour << " hours, " << min << " minutes, "
<< sec << " seconds" << endl;
}
int main()
{
Timer T; // object declared;
T.sec = T.min = T.sec = 0;
T.clockTick(); // 1st member function called
T.displayTimer(); // 2nd member function
return 0;
}
  • Line 21: A T object of the Timer class is declared.

  • Line 22: The attributes of T are initialized to zero.

  • Line 23: The timer is started by calling the clickTick() function using the . operator.

  • Line 20: The timer is displayed on the console using the displayTimer() function and the . operator.

Tip: Run the above program, and you'll see logical error reporting on lines 22, 23, and 24. What does it say?

In the last three lines of the main() function, errors arise for attempting to directly access private member variables of the Timer class. In C++, by default, member variables and functions declared within a class are considered private, meaning they are not accessible from outside the class. In the given code, sec, min, and hour are private member variables of the Timer class, so trying to assign values to them using the syntax T.sec = T.min = T.sec = 0 is not allowed.

Remember: In C++, by default, member variables and functions declared within a class are considered private if no access specifier (public) is used. This means that they are not accessible from outside the class by default.

So, how can we get access to the private attributes of the T object? Access specifiers are the answer. Let’s look into this concept and understand why it works so well.

Access specifiers

Access specifiers in C++ (such as public and private) play a crucial role in controlling the accessibility of class members. By dividing the class into public and private sections, these access specifiers determine which members can be accessed and modified from outside the class through its objects. In C++, if no access specifier is explicitly specified, private is the default access specifier for the class members. This means that without specifying any access specifier, all the members of the class are considered private and can’t be accessed from outside the class. Access specifiers help enforce data encapsulation by hiding the internal implementation details and restricting direct modification of class members by external code.

So, how useful are classes like this when we can’t access their members? Such classes enforce data encapsulation and abstraction. They hide all the internal implementation details and restrict the modification of their members from the user. But how can we call a specific class function from outside the class if all the class member functions are private and we do not have any access to them? To access them, we need at least one public function that can act as an interface to invoke private functions.

A general syntax is given below.

Press + to interact
class ClassName
{
private:
void privateFunction()
{
// Implementation of the private function
}
public:
void publicFunction()
{
// Call the private function
privateFunction();
}
};

This is the only way to access private members while maintaining encapsulation.

In our Timer class example, the code will look like this:

Press + to interact
#include<iostream>
using namespace std;
class Timer
{
private:
// Member attributes
int sec, min, hour;
// Member functions
void setTimer();
void displayTimer();
public:
int callFunctions();
};
void Timer::setTimer()
{
sec = min = hour = 0;
}
void Timer::displayTimer()
{
cout << "Time: " << hour << " hours, " << min << " minutes, "
<< sec << " seconds" << endl;
}
int Timer::callFunctions()
{
setTimer();
displayTimer();
}
int main()
{
Timer T; // object declared;
T.callFunctions(); // 2nd member function
return 0;
}

In this example, the function callFunctions() is declared in the public section of the Timer class, while the remaining members (attributes and functions) are declared in the private section. This public function callFunctions() serves as an interface between the private members of the class (which may include private data members and functions) and the user who calls the public function through an instance/object of the class. It provides a way to indirectly access and interact with the private members in a controlled manner.

In general, it is considered good practice to make member attributes private and member functions public. Following this practice, the Timer class should be defined as follows:

Press + to interact
#include<iostream>
using namespace std;
class Timer
{
private:
// Member attributes
int sec, min, hour;
public:
// Member functions
void setTimer();
void displayTimer();
};
void Timer::setTimer()
{
sec = min = hour = 0;
}
void Timer::displayTimer()
{
cout << "Time: " << hour << " hours, " << min << " minutes, "
<< sec << " seconds" << endl;
}
int main()
{
Timer T; // object declared;
T.setTimer(); // member function
T.displayTimer();
return 0;
}

Look at the following code:

Press + to interact
// Imagine the above Timer class is available.
int main()
{
Timer T1;
Timer T2;
cout << "Calling setTimer() for T1 and T2\n";
T1.setTimer();
T2.setTimer();
cout << "Calling displayTimer() for T1 and T2\n";
T1.displayTimer();
T2.displayTimer();
return 0;
}

We’ve called the functions in the Timer class namely Timer::setTimer() and Timer::displayTimer(). How do the setTimer() calls on lines 9–10 and the displayTimer() calls on lines 13–14, distinguish which attributes inside these functions are accessed for T1 or T2? If we look at the two functions’ implementation, it never says T1.sec or T2.sec or other attributes.

The this pointer

The concept of the this pointer in C++ is used to refer to the current object instance within a member function of a class. It is an implicit pointer that holds the memory address of the object for which the member function is called. In the background, the compiler translates the member function calls and passes the address of the callee object as a hidden argument, which is received by the this pointer.

Press + to interact
#include<iostream>
using namespace std;
class Timer
{
private:
// Member attributes
int sec, min, hour;
public:
// Member functions
void setTimer(); // Backend: void setTime(Timer* this)
void displayTimer(); // Backend: void displayTimer(Timer* this)
};
void Timer::setTimer() // void Timer::setTimer(Timer* this)
{
sec = min = hour = 0; // this->sec = this->min = this->hour = 0;
}
void Timer::displayTimer()// void Timer::displayTimer(Timer* this)
{
cout << "Time: " << hour << " hours, " << min << " minutes, "
<< sec << " seconds" << endl;
cout << "\nUsing *this\n"<<endl;
cout << "Time: " << this->hour << " hours, " << this->min << " minutes, "
<< this->sec << " seconds" << endl;
}
int main()
{
Timer T1;
Timer T2;
cout << "Calling setTimer() for T1 and T2\n";
T1.setTimer(); // Timer::setTimer(&T1);
T2.setTimer(); // Timer::setTimer(&T2);
cout << "Calling displayTimer() for T1 and T2\n";
T1.displayTimer(); // Timer::displayTimer(&T1);
T2.displayTimer(); // Timer::displayTimer(&T2);
return 0;
}

In the above example, the member functions setTimer() and displayTimer() of the Timer class are defined. When these functions are called, the compiler implicitly passes the address of the object (&T1 or &T2) as the hidden parameter, and it is received by the this pointer in the respective member functions.

For example, the function setTimer() in the backend is translated as void Timer::setTimer(Timer* this), where this represents the address of the object that has called this member function. Inside the function, accessing the member attributes (sec, min, hour) is translated to this->sec, this->min, and this->hour. This allows the function to access and modify the attributes of the specific object it is called upon.

Similarly, the function displayTimer() in the backend is translated as void Timer::displayTimer(Timer* this). Inside this function, this->hour, this->min, and this->sec are used to access the attributes of the object and display them.

So, when T1.setTimer() is called, it is translated as Timer::setTimer(&T1), and the this pointer inside the function holds the address of T1. Likewise, T2.setTimer() is translated as Timer::setTimer(&T2).The this pointer is useful in distinguishing between different instances of the same class and allows member functions to access and manipulate the specific attributes of the object on which they are called. It provides a way to reference the current object within the scope of the member function and enables data encapsulation and object-oriented programming principles.

Let’s visualize the complete working of the this pointer:

Press + to interact
canvasAnimation-image
1 of 4

Accessing the member variables

The this pointer can be strategically used to differentiate between member variables and local variables with the same name. What does that mean?

Press + to interact
class Timer
{
private:
int sec, min, hour;
public:
// Parameterized constructor
void setTimer(int sec, int min, int hour)
{
sec = sec, min = min, hour = hour;
}
};

In the above example, the passed parameter names and the member variable names are the same. So, how do we differentiate between these two? This is where the this pointer comes in handy.

Press + to interact
class Timer
{
private:
int sec, min, hour;
public:
// Parameterized constructor
void setTimer(int sec, int min, int hour)
{
this->sec = sec, this->min = min, this->hour = hour;
}
};

By assigning the this pointer to the member variables, we now know that the variable at the left-hand side of the assignment operator is a member variable. And so we are able to avoid the naming conflicts between parameters and member variables.

Also, it is considered as a good programming practice to use this-> to access any attribute of the class inside any member function of the class.