The purpose of get and set methods in C++
This article explains the purpose of get and set methods. As you will see, the use of these methods is directly connected to access specifiers (public, protected and private) and in the following paragraphs you are going to see exactly how.
What if everything was declared public
Let me show you the use of the access specifiers on our Skyscraper example. Precisely, let me show you what would happen if we placed everything into the public section of the class. Look at the file skyscraper.h. Notice the variable 'height' in the public section of the header.
#ifndef SKYSCRAPER_H #define SKYSCRAPER_H class Skyscraper { public: double height; }; #endif // SKYSCRAPER_H
Now have a look at the main.cpp below. Because I placed the height in the public section of the class declaration, it can be accessed directly and thus changed using s.height = ... You will soon realize that this was a bad idea. Look, I could easily change the height of a skyscraper to -10000.0 m. But there are no skyscrapers with negative height!
#include <iostream> #include "skyscraper.h" int main() { Skyscraper s; s.height = -10000.0; std::cout << s.height << '\n'; return 0; }
Of course, you could check the height before you assign it. But in such a case you would have to do it directly in the main class and that is not a very good design. Probably you are asking, why would I want to use -10000 as the height of a skyscraper? This can happen unintentionally, e.g. by reading the wrong column of a file. Or simply because you happened to enter an incorrect formula into the function that calculates the height from the number of floors!
Now imagine the consequences of a negative height! All the functions using the height would get absurd results!
The set and get method
To solve the problem of a possibly absurd height, you will have to place the height into the private (or protected) section of the header. Now you can no longer change the height by writing s.height = ... in the main.cpp. You will get a compilation error. So how do you assign a value to the height and how will you retrieve it?
To that end we use accessor and mutator methods (get and set). In our case, we will call them getHeight and setHeight. In theory, you can call these functions whatever you like, but calling them get and set makes their functionality understandable. Now, look at the header file below. You can see that the height moved into the private section and we have two more methods in the public section.
#ifndef SKYSCRAPER_H #define SKYSCRAPER_H #include "assert.h" class Skyscraper { public: void setHeight(double h); double getHeight() const {return height;} private: double height; }; #endif // SKYSCRAPER_H
The set method starts with void, because it does not return any values. If you look at the source file, you will see that the method assigns the value in the argument 'h' to the member variable 'height'. Before that, it checks that the value that we are planning to assign is non-negative. In this example we are using the function assert() from "assert.h" to accomplish this check. assert() terminates the program (usually with a message) if its argument evaluates to false. The source file skyscraper.cpp is below:
#include "skyscraper.h" void Skyscraper::setHeight(double h){ assert(h >= 0); this->height = h; }
Assertions are only enabled during the debugging phase and disabled for release builds. Assertions are really meant for checking something that the developer believes is true at a certain point in the program and for situations that are under his full control. E.g. checking whether the height of the Skyscraper is obtained from the correct column of a file or computed with a correct formula are completely under the control of the developer.
Arguments of public methods (in our case mutators) are usually not under the full control of the developer. For issues that are not under the control of the developer, using exceptions (standalone or together with assertions) is the preferred solution. In this case we are using std::invalid_argument() which requires to #include <stdexcept>. This can be seen below:
#include "skyscraper.h" void Skyscraper::setHeight(double h){ if(h < 0) throw std::invalid_argument("Height must be non-negative."); this->height = h; }
Now you have seen why the set method is so useful, i.e. to check whether the values we are about to assign make any sense.
Now let's look at the get method. Get method starts with double because it returns the value of the height. It takes no arguments and unlike the set method, it ends with const. The const is an assurance that the method does not change the object to which it belongs. In simple terms, if we wrote into the body of the get function something that would try to modify the value of the height, the compiler would complain.
In the main.cpp below, you can see how we now use our new methods. I commented out the old lines as they would not compile with our new code. In this case, the set method sets the height to 120.0 and the getHeight() retrieves the very same value for the print. If you try to set the value to -10000, you will get a "Assertion failed: h >= 0" message (or a similar statement with std::invalid_argument() for release build).
#include <iostream> #include "skyscraper.h" int main() { Skyscraper s; // s.height = -10000.0; // std::cout << s.height << '\n'; s.setHeight(120); std::cout << s.getHeight() << '\n'; return 0; }
Probably you noticed that in case of the get method, I placed both the declaration and definition (i.e. the body) of the function inside the class block of the header file. This is very common for short functions (thus both for get and set methods). In our case, the set method was slightly longer, that is why I placed its body into the source file. The advantage of placing the function body into the source file, is that it gets compiled only once.
Set method with 'this' pointer
Often you will encounter a slightly different way of writing of the set method, which makes use of the pointer 'this'. This type of methods can be spotted easily, as they usually give the same name to the member variable and to the method argument. Notice the new definition of our set method below. The method argument changed from 'h' to 'height'. How will we now distinguish between the method argument 'height' and the member variable 'height'?
void setHeight(double height);
Precisely for this purpose we use the pointer 'this'. For the moment you only need to know that 'this' is a pointer to the object whose methods are being defined. *this (notice the extra asterisk) is an object residing at an address this. In our case I will make use of the pointer 'this' in the following way: I will refer as 'height' to the method argument previously called 'h' and I will refer to the member variable previously called 'height' as '(*this).height'.
This is the original method setHeight():
void Skyscraper::setHeight(double h){ if(h < 0) throw std::invalid_argument("Height must be non-negative."); this->height = h; }
Compare it now with the new method setHeight():
void Skyscraper::setHeight(double height){ if(height < 0) throw std::invalid_argument("Height must be non-negative."); (*this).height = height; }
Even more often, you will see (*this).height written as this->height. Thus the code will change to
void Skyscraper::setHeight(double height){ if(height < 0) throw std::invalid_argument("Height must be non-negative."); this->height = height; }
Now you know to what purpose we use access specifiers, such as public and private. Also, you learned what the access methods get and set serve!
Tagged: C++