Classes: Syntax and the this Pointer
Learn about classes and the this pointer.
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;
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 variablesdataType member1;...// Member functionsfuncType function();...}
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 attributesint sec, min, hour;// Member functionsvoid clockTick();void displayTimer();};
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}};
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 attributesint sec, min, hour;// Member functionsvoid clockTick(){// Function body}void displayTimer(){// Function body}};
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}
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 attributesint sec, min, hour;// Member functions declarationvoid clockTick();void displayTimer();};// Member function definitionvoid Timer::clockTick(){//Function body}int Timer::displayTimer(){//Function body}
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;
Similar to any data type variable, to access the public
members of the object, we use the .
(dot) operator.
objectName.memberFunction();
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;
Execute the following program, and you’ll see a surprise.
#include <iostream>using namespace std;class Timer{// Member attributesint sec, min, hour;// Member functionsvoid 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 calledT.displayTimer(); // 2nd member functionreturn 0;}
Line 21: A
T
object of theTimer
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.
class ClassName{private:void privateFunction(){// Implementation of the private function}public:void publicFunction(){// Call the private functionprivateFunction();}};
This is the only way to access private
members while maintaining encapsulation.
In our Timer
class example, the code will look like this:
#include<iostream>using namespace std;class Timer{private:// Member attributesint sec, min, hour;// Member functionsvoid 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 functionreturn 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:
#include<iostream>using namespace std;class Timer{private:// Member attributesint sec, min, hour;public:// Member functionsvoid 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 functionT.displayTimer();return 0;}
Look at the following code:
// 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.
#include<iostream>using namespace std;class Timer{private:// Member attributesint sec, min, hour;public:// Member functionsvoid 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:
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?
class Timer{private:int sec, min, hour;public:// Parameterized constructorvoid 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.
class Timer{private:int sec, min, hour;public:// Parameterized constructorvoid 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.