You might have noticed that once we slap private
onto the Player
class definition, we can no longer read or write the name of the player from outside the Player
class.
If we try and read the name with the following code:
Player me; cout << me.name << endl;
Or write to the name, as follows:
me.name = "William";
Using the struct Player
definition with private
members, we will get the following error:
main.cpp(24) : error C2248: 'Player::name' : cannot access private member declared in class 'Player'
This is just what we asked for when we labeled the name
field private
. We made it completely inaccessible outside the Player
class.
A getter (also known as an accessor function) is used to pass back copies of internal data members to the caller. To read the player's name, we'd deck out the Player
class with a member function specifically to retrieve a copy of that private
data member:
class Player { private: string name; // inaccessible outside this class! // rest of class as before public: // A getter function retrieves a copy of a variable for you string getName() { return name; } };
So now it is possible to read the player's name information. We can do this by using the following code statement:
cout << player.getName() << endl;
Getters are used to retrieve private
members that would otherwise be inaccessible to you from outside the class.
Real world tip–Keyword const
Inside a class, you can add the const
keyword to a member function declaration. What the const
keyword does is promises to the compiler that the internal state of the object will not change as a result of running this function. Attaching the const
keyword will look something like this:
string getName() const { return name; }
No assignments to data members can happen inside a member function that is marked const
. As the internal state of the object is guaranteed not to change as a result of running a const
function, the compiler can make some optimizations around function calls to const
member functions.
A setter (also known as a modifier function or mutator function) is a member function whose sole purpose is to change the value of an internal variable inside the class, as shown in the following code:
class Player { private: string name; // inaccessible outside this class! // rest of class as before public: // A getter function retrieves a copy of a variable for you string getName() { return name; } void setName( string newName ) { name = newName; } };
So we can still change the private
function of a class
from outside the class
function, but only if we do so through a setter function.
So the first question that crosses a newbie programmer's mind when he first encounters get/set operations on private
members is, isn't get/set self-defeating? I mean, what's the point in hiding access to data members when we're just going to expose that same data again in another way? It's like saying, "You can't have any chocolates because they are private, unless you say please getMeTheChocolate()
. Then, you can have the chocolates."
Some expert programmers even shorten the get/set functions to one liners, like this:
string getName(){ return name; } void setName( string newName ){ name = newName; }
Let's answer the question. Doesn't a get/set pair break encapsulation by exposing the data completely?
The answer is twofold. First, get member functions typically only return a copy of the data member being accessed. This means that the original data member's value remains protected and is not modifiable through a
get()
operation.
Set()
(mutator method) operations are a little bit counterintuitive though. If the setter is a passthru
operation, such as void setName( string newName ) { name=newName; }
, then having the setter might seem pointless. What is the advantage of using a mutator method instead of overwriting the variable directly?
The argument for using mutator methods is to write additional code before the assignment of a variable to guard the variable from taking on incorrect values. Say, for example, we have a setter for the hp
data member, which will look like this:
void setHp( int newHp ) { // guard the hp variable from taking on negative values if( newHp < 0 ) { cout << "Error, player hp cannot be less than 0" << endl; newHp = 0; } hp = newHp; }
The mutator method is supposed to prevent the internal hp
data member from taking on negative values. You might consider mutator methods a bit retroactive. Should the responsibility lie with the calling code to check the value it is setting before calling setHp( -2 )
, and not let that only get caught in the mutator method? Can't you use a public
member variable and put the responsibility for making sure the variable doesn't take on invalid values in the calling code, instead of in the setter? You can.
However, this is the core of the reason behind using mutator methods. The idea behind mutator methods is so that the calling code can pass any value it wants to the setHp
function (for example, setHp( -2 )
), without having to worry whether the value it is passing to the function is valid or not. The setHp
function then takes the responsibility of ensuring that the value is valid for the hp
variable.
Some programmers consider direct mutator functions such as getHp()
/setHp()
a code smell. A code smell is in general a bad programming practice that people don't overtly take notice of, except for a niggling feeling that something is being done suboptimally. They argue that higher-level member functions can be written instead of mutators. For example, instead of a setHp()
member function, we should have public
member functions such as heal()
and damage()
instead. An article on this topic is available at http://c2.com/cgi/wiki?AccessorsAreEvil.
3.17.81.201