Classes: Constructors/Destructors and Getters/Setters
Learn about constructors/destructors and getters/setters.
Constructors, destructors, getters, and setters are fundamental object-oriented programming (OOP) components. Constructors enable proper initialization of objects, while destructors handle cleanup operations. Getters and setters facilitate controlled access to class member variables. This lesson provides a comprehensive overview of these elements, emphasizing their significance in creating robust and maintainable code. We’ll explore different types of constructors, including default, parameterized, and those with default parameters or initialization lists. Additionally, we’ll discuss the role of destructors in resource management. Furthermore, we’ll explore the importance of getters and setters for encapsulation and data manipulation.
Constructors and destructors
First, let’s review why we need constructors and what their benefits are.
Constructor
A constructor is a member function with the same name as the class. It is automatically called when an object of a class is made. The main concept behind the constructor is to initialize the object data members and establish their initial state. Constructors provide flexibility to create objects differently, accepting different sets of parameters depending on the constructor type. There are many types of constructors, including:
Default constructors
Parameterized constructors
Default constructors with default parameters
Member initialization lists
Before we go into detail for each type, let’s first see why they’re so important. The code below is the Timer
class implementation without any constructors. Let’s run the code.
#include <iostream>using namespace std;class Timer{private:// Member attributesint sec, min, hour;public:// Displays timevoid displayTimer(){cout << "Time: " << hour << " hours, " << min << " minutes, "<< sec << " seconds" << endl;}};int main(){Timer timer1; // Create a timer object with default constructortimer1.displayTimer();return 0;}
Lines 12–16: A
displayTimer()
helper function is defined that prints the value of the timer.Line 21: A
timer1
object of theTimer
class is defined.Line 22: The values of the
timer1
object are printed.
The code gives us a garbage value for each member, meaning that they are not yet initialized. To avoid that, we prefer that the values of the members be initialized when an object is created. To achieve that, we introduce the concept of constructors.
Default constructor
Definition: Default constructors are used to create objects without any arguments.
Syntax:
ClassName() {}
Example code:
class Timer {private:int sec, min, hour;public:Timer(){sec = 0;min = 0;hour = 0;}};int main(){Timer T; // this will call automatically Timer()return 0;}
Parameterized constructor
Definition: Parameterized constructors accept arguments to initialize object attributes.
Syntax:
ClassName(type1 arg1, type2 arg2, ...) {}
Example code:
class Timer {private:int sec, min, hour;public:Timer(int h, int m, int s){hour = h;min = m;sec = s;}};int main(){Timer T(1,2,3); // Call Timer(1,2,3): where 1 will be received in h, 2 in m and 3 in sreturn 0;}
Constructors with default parameters
Definition: Constructors with default parameter values allow objects to be created with fewer arguments.
Syntax:
ClassName(type1 arg1 = defaultValue1, type2 arg2 = defaultValue2, ...) {}
Example code
class Timer{private:int sec, min, hour;public:Timer(int h = 0, int m = 0, int s = 0){hour = h;min = m;sec = s;}};int main(){Timer T1; // Will automatically call Timer(0,0,0)Timer T2(1); // Will automatically call Timer(1,0,0)Timer T3(1,2); // Will automatically call Timer(1,2,0)Timer T4(1,2,3); // Will automatically call Timer(1,2,3)return 0;}
4. Constructors with initialization lists
Definition: Constructors with initialization lists offer a convenient way to directly initialize class member variables. This approach is particularly recommended when there is a constant attribute within the class that should never be modified after initialization. By utilizing member initialization lists, values can be assigned to object components as they are declared and initialized simultaneously. This ensures efficient and effective initialization of class attributes.
Syntax:
ClassName(type1 arg1, type2 arg2, ...) : memberVariable1(arg1), memberVariable2(arg2), ... {}
Example code:
class Timer {private:int sec, min, hour;const string name; // the name of the object should not be changedpublic:Timer(int h, int m, int s, string _n) : hour(h), min(m), sec(s), name(_n){// Constructor code}};int main(){Timer t(1,2,3,"t"); // will in the backend initialize (hour=1, min = 2, sec = 3, and some_constant = 4)}
Running example of all the constructors
Here are the examples of all four types of constructors and how to use them in one example.
#include <iostream>using namespace std;class Timer{private:// Member attributesint sec, min, hour;public:// Default constructorTimer(){hour = 0;min = 0;sec = 0;}// Displays timevoid displayTimer(){cout << "Time: " << hour << " hours, " << min << " minutes, "<< sec << " seconds" << endl;}};int main(){Timer T; // This will call default constructorT.displayTimer();return 0;}
Tip: Execute the above four files separately and see their impact.
Destructors
Definition: Destructors are special member functions that are automatically called when an object is destroyed.
Syntax:
~ClassName() {}
Example code:
#include<string>#include<iostream>using namespace std;class Timer{private:// Member attributesint sec, min, hour;string name;public:// DestructorTimer(string n): name(n){cout << name << " is created "<<endl;}~Timer(){cout << name << " is destroyed "<<endl;}};Timer TGlobal("TGlobal");int main(){Timer T1("T1");{Timer T2("T2");{Timer T3("T3");static Timer TStatic("TStatic");} // T3 will be destroyed but not T4, because it is a static object} // T2 will be destroyedTimer T5("T5");return 0;} // T5 will be destroyed first and then T1// (within a scope the objects are destroyed in reverse order of creation)// Then TStatic and TGlobal will be destroyed,// as global/static variables are destroyed in the end// again in reverse order
Tip: Execute the above code and see the order in which the objects are created and destroyed.
Explanation
By observing the object creation and destruction sequence upon execution, we can understand how objects behave within different scopes and how global and static objects are handled during the program’s lifetime.
The global object
TGlobal
is created and displayed as theTGlobal is created
string. It resides in the global segment and will be destroyed at the end of the program.Inside the
main()
function, theT1
local object is created and displayed as theT1 is created
string.Within the nested scopes,
T2
is created, followed byT3
. Both objects are displayed as theT2 is created
andT3 is created
strings, respectively.The
static
objectTStatic
is created within the innermost scope. It persists beyond the scope due to its static nature and is displayed as theTStatic is created
string.As the program reaches the end of each scope, the objects are destroyed in the reverse order of their creation. First,
T3
is destroyed, thenT2
. SinceTStatic
is a static object, it remains intact.Moving back to the outer scopes,
T2
is destroyed next, followed byT1
. Within each scope, the objects are destroyed in the reverse order of their creation.Finally,
T5
is created and displayed as theT5 is created
string. At the end of the program,T5
is destroyed, followed byT1
. Finally, the static objectTStatic
and the global objectTGlobal
are destroyed because global and static variables are destroyed at the end of the program in reverse order.
Let’s visualize the code:
Getters and setters
Getters and setters play a crucial role in object-oriented programming by providing controlled access to class member variables. Getters are methods that retrieve the values of these variables, while setters are methods that allow for their modification. By encapsulating the variables and exposing them through getters and setters, we can maintain data integrity and control how the data is accessed and modified.
Getters, also known as accessor methods, are used to retrieve the values of private member variables. By convention, getters are typically declared as constant member functions, indicated by the const
keyword at the end of the function declaration. This ensures that the getter functions do not modify the state of the object they are called on.
Similarly, setters give the user the access to modify the attributes of the object. It is the responsibility of the programmer of the class that any illegal update should be avoided. For example, the Timer
class has private
member attributes, including sec
(seconds), min
(minutes), and hour
(hours). To ensure valid resetting, we can include range checks in the setters. For instance, when setting the hour, we can verify that the value falls within the range of 0 to 23. Similarly, we can validate the minute and second values to ensure they are between 0 and 59.
Here’s an updated example code incorporating getters and setters with proper range checking for the Timer
class:
#include <iostream>using namespace std;class Timer {private:int sec, min, hour;public:// ConstructorTimer(int h = 0, int m = 0, int s = 0) {setHour(h);setMin(m);setSec(s);}// Getters (const functions)int getHour() const {return hour;}int getMin() const {return min;}int getSec() const {return sec;}// Settersvoid setHour(int h) {if (h >= 0 && h <= 23) {hour = h;} else {hour = 0;cout << "Invalid hour value!" << endl;}}void setMin(int m) {if (m >= 0 && m <= 59) {min = m;} else {min = 0;cout << "Invalid minute value!" << endl;}}void setSec(int s) {if (s >= 0 && s <= 59) {sec = s;} else {sec = 0;cout << "Invalid second value!" << endl;}}// Displays timevoid displayTimer() const {cout << "Time: " << hour << " hours, " << min << " minutes, "<< sec << " seconds" << endl;}};int main() {Timer t(1, 30, 50);t.displayTimer(); // Output: Time: 1 hours, 30 minutes, 50 seconds// Get values using getterscout << "Hour: " << t.getHour() << endl; // Output: Hour: 1cout << "Minute: " << t.getMin() << endl; // Output: Minute: 30cout << "Second: " << t.getSec() << endl; // Output: Second: 50return 0;}
In the above example, the Timer
class includes getters (getHour()
, getMin()
, getSec()
) and setters (setHour()
, setMin()
, setSec()
) as its member variables. The getters are declared as const
functions, ensuring they do not modify the state of the Timer
object. They allow us to retrieve the values of the hour
, min
, and sec
variables, respectively.
By using getters and setters, we encapsulate the internal state of the Timer
object and provide controlled access to the data. The setters perform input validation to ensure the assigned values are within valid ranges, while the getters allow other parts of the program to retrieve the values without directly accessing or modifying the member variables. This promotes data encapsulation, code maintainability, and flexibility in working with the Timer
objects.
Remember: Although the destructor may initially seem less crucial, its significance becomes evident in upcoming lessons when dealing with dynamically allocated data within a class. In such scenarios, the destructor plays a vital role as it becomes responsible for releasing heap memory, ensuring proper memory management. This is a critical aspect to consider when working with objects that allocate memory dynamically. As we progress to the next chapter on operator overloading, you'll witness the imperative need for a destructor in specific situations, further emphasizing its importance in C++ programming.