In This Chapter
Declaring members protected
Accessing protected members from within the class
Accessing protected members from outside the class
Chapter 12 introduces the concept of the class. That chapter describes the public
keyword as though it were part of the class declaration — just something you do. In this chapter, you find out about an alternative to public
.
The members of a class can be marked protected, which makes them inaccessible outside the class. The alternative is to make the members public. Public members are accessible to all.
Please understand the term inaccessible in a weak sense. Any programmer can go into the source code, remove the protected
keyword, and do whatever she wants. Further, any hacker worth his salt can code into a protected section of code. The protected
keyword is designed to protect a programmer from herself by preventing inadvertent access.
To understand the role of protected, think about the goals of object-oriented programming:
To protect the internals of the class from outside functions. Suppose, for example, that you have a plan to build a software microwave (or whatever), provide it with a simple interface to the outside world, and then put a box around it to keep others from messing with the insides. The protected keyword is that box.
To make the class responsible for maintaining its internal state. It's not fair to ask the class to be responsible if others can reach in and manipulate its internals (any more than it's fair to ask a microwave designer to be responsible for the consequences of my mucking with a microwave's internal wiring).
To limit the interface of the class to the outside world. It's easier to figure out and use a class that has a limited interface (the public members). Protected members are hidden from the user and need not be learned. The interface becomes the class; this is called abstraction (see Chapter 11 for more on abstraction).
To reduce the level of interconnection between the class and other code. By limiting interconnection, you can more easily replace one class with another or use the class in other programs.
Now, I know what you functional types out there are saying: "You don't need some fancy feature to do all that. Just make a rule that says certain members are publicly accessible and others are not."
Although that is true in theory, it doesn't work. People start out with all kinds of good intentions, but as long as the language doesn't at least discourage direct access of protected members, these good intentions get crushed under the pressure to get the product out the door.
Adding the keyword public
to a class makes subsequent members public, which means that they are accessible by nonmember functions. Adding the keyword protected
makes subsequent members of the class protected, which means they are not accessible by nonmembers of the class. You can switch between public
and protected
as often as you like.
Suppose you have a class named Student
. In this example, the following capabilities are all that a fully functional, upstanding Student
needs (notice the absence of spendMoney()
and drinkBeer()
— this is a highly stylized student):
addCourse
(inthours, float grade
) — adds a course
grade()
— returns the current grade point average
hours()
— returns the number of hours earned toward graduation
The remaining members of Student
can be declared protected to keep other functions' prying expressions out of Student
's business.
class Student { public: // grade - return the current grade point average float grade() { return gpa; } // hours - return the number of semester hours int hours() { return semesterHours; } // addCourse - add a course to the student's record float addCourse(int hours, float grade); // the following members are off-limits to others protected: int semesterHours; // hours earned toward graduation float gpa; // grade point average };
Now the members semester hours
and gpa
are accessible only to other members of Student
. Thus, the following doesn't work:
Student s; int main(int argcs, char* pArgs[]) { // raise my grade (don't make it too high; otherwise, no // one would believe it) s.gpa = 3.5; // <- generates compiler error float gpa = s.grade(); // <- this public function reads // a copy of the value, but you // can't change it from here return 0; }
The application's attempt to change the value of gpa
is flagged with a compiler error.
It's considered good form not to rely on the default and specify either public
or protected
at the beginning of the class. Most of the time, people start with the public members because they make up the interface of the class. Protected members are saved until later.
A class member can also be protected by declaring it private
. In this book, I use the protected
keyword exclusively. The difference between private and protected has to do with inheritance, which is presented in Chapter 19.
Now that you know a little more about how to use protected members in an actual class, I can replay the arguments for using protected members.
Making the gpa
member protected precludes the application from setting the grade point average to some arbitrary value. The application can add courses, but it can't change the grade point average.
If the application has a legitimate need to set the grade point average directly, the class can provide a member function for that purpose, as follows:
class Student { public: // same as before float grade() { return gpa; } // here we allow the grade to be changed float grade(float newGPA) { float oldGPA = gpa; // only if the new value is valid if (newGPA > 0 && newGPA <= 4.0) { gpa = newGPA; } return oldGPA; } // ...other stuff is the same including the data members: protected: int semesterHours; // hours earned toward graduation float gpa; };
The addition of the member function grade(float)
allows the application to set the gpa
. Notice, however, that the class still hasn't given up control completely. The application can't set gpa
to any old value; only a gpa
in the legal range of values (from 0 through 4.0) is accepted.
Thus, the Student
class has provided access to an internal data member without abdicating its responsibility to make sure that the internal state of the class is valid.
A class provides a limited interface. To use a class, you need to know only its public members as well as what they do and their arguments. This can drastically reduce the number of things you need to master and remember to use the class.
As conditions change or as bugs are found, you want to be able to change the internal workings of a class. Changes to those details are less likely to require changes in the external application code if you can hide the internal workings of the class.
A second, perhaps more important, reason lies in the limited ability of humans (I can't speak for dogs and cats) to keep a large number of things in their minds at any given instant. Using a strictly defined class interface allows the programmer to forget the details that go on behind it. Likewise, a programmer building the class need not concentrate to quite the same degree on exactly how each of the functions is being used.
Occasionally, you want a nonmember function to have access to the protected members of a class. You do so by declaring the function to be a friend of the class by using the keyword friend
.
The friend
declaration appears in the class that contains the protected member. The friend
declaration is like a prototype declaration in that it includes the extended name and the return type. In the following example, the function initialize()
can now access anything it wants in Student
:
class Student { friend void initialize(Student*); public: // same public members as before... protected: int semesterHours; // hours earned toward graduation float gpa; };
// the following function is a friend of Student // so it can access the protected members void initialize(Student *pS) { pS->gpa = 0; // this is now legal... pS->semesterHours = 0; // ...when it wasn't before }
A single function can be declared a friend of two classes at the same time. Although this can be convenient, it tends to bind the two classes together. This binding of classes is normally considered bad because it makes one class dependent on the other. If the two classes naturally belong together, however, it's not all bad, as shown here:
class Student; // forward declaration class Teacher { friend void registration(Teacher& t, Student& s); public: void assignGrades(); protected: int noStudents; Student *pList[100]; }; class Student { friend void registration(Teacher& t, Student& s); public: // same public members as before... protected: Teacher *pT; int semesterHours; // hours earned toward graduation float gpa; }; void registration(Teacher& t, Student& s) { // initialize the Student object s.semesterHours = 0; s.gpa = 0; // if there's if (t.noStudents < 100) { // ...add it onto the end of the list t.pList[t.noStudents] = &s; t.noStudents++; } }
In this example, the registration()
function can reach into both the Student
and Teacher
classes to tie them together at registration time, without being a member function of either one.
The first line in the example declares the class Student
, but none of its members. This is called a forward declaration and just defines the name of the class so that other classes, such as Teacher
, can define a pointer to it. Forward declarations are necessary when two classes refer to each other.
A member function of one class may be declared a friend of another class, as shown here:
class Teacher { // ...other members as well... public: void assignGrades(); }; class Student { friend void Teacher::assignGrades(); public: // same public members as before... protected: int semesterHours; // hours earned toward graduation float gpa; }; void Teacher::assignGrades() { // can access protected members of Teacher from here }
Unlike in the nonmember example, the member function assignGrades()
must be declared before the class Student
can declare it to be a friend.
An entire class can be named a friend of another. This has the effect of making every member function of the class a friend:
class Student; // forward declaration class Teacher { protected: int noStudents; Student *pList[100]; public: void assignGrades(); }; class Student
{ friend class Teacher; // make entire class a friend public: // same public members as before... protected: int semesterHours; // hours earned toward graduation float gpa; };
Now, any member function of Teacher
has access to the protected members of Student
. Declaring one class a friend of the other inseparably binds the two classes together.
3.145.125.51