Chapter 24

Do Not Disturb: Protected Members

In This Chapter

arrow Protecting members of a class

arrow Asking, “Why do that?”

arrow Declaring friends of the class

My goal with this part of the book, starting with Chapter 21, is to model real-world objects in C++ by using the class structure. In Chapter 22, I introduce the concept of member functions in order to assign active properties to the classes. Returning to the microwave oven example in Chapter 21, assigning active properties allows me to give my Oven class properties like cook() and defrost().

However, that’s only part of the story. I still haven’t put a box around the insides of my classes to ward off meddling. I can’t very well hold someone responsible if the microwave catches on fire so long as the insides are exposed to anyone who wants to mess with them.

This chapter “puts a box” around the classes by declaring certain members off-limits to user functions.

Protecting Members

Members of a class can be flagged as inaccessible from outside the class with the keyword protected. This is in direct opposition to the public keyword, which designates those members that are accessible to all functions. The public members of a class form the interface to the class (think of the keypad on the front of the microwave oven) while the protected members form the inner workings (“no user-serviceable parts inside”).

technicalstuff.eps There is a third category called private. The only difference between private and protected members is the way they react to inheritance, a concept I present in Chapter 28.

Why you need protected members

Declaring a member protected allows a class to put a protective box around the class. This makes the class responsible for its own internal state. If something in the class gets screwed up, the author of the class has nowhere to look except herself. It’s not fair, however, to ask the programmer to take responsibility for the state of the class if any ol’ function can reach in and muck with it.

In addition, limiting the interface to a class makes the class easier to learn for programmers that use that interface in their programs. In general, I don’t really care how my microwave works inside as long as I know how to use the controls. In a similar fashion, I don’t generally worry about the inner workings of library classes as long as I understand the arguments to the public member functions.

Finally, limiting the class interface to just some choice public functions reduces the level of coupling between the class and the application code.

Note: Coupling refers to how much knowledge the application has of how the class works internally, and vice versa. A tightly coupled class has intimate knowledge of the surrounding application — and uses that knowledge. A loosely coupled class works only through a simple, generic public interface. A loosely coupled class knows little about its surroundings and hides most of its own internal details as well. Loosely coupled classes are easier to test and debug — and easier to replace when the application changes.

I know what you procedural 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.” This is true in theory, and I’ve even been on projects that employed such rules, but in practice it doesn’t work. People start out with good intentions, but as long as the language doesn’t at least discourage direct access to protected members, these good intentions get crushed under the pressure to get the product out the door.

Making members protected

Adding the keyword public: to a class makes subsequent members publicly accessible. Adding the keyword protected: makes subsequent members protected, which means they are accessible only to other members of the same class or functions that are specifically declared friends (more on that later in this chapter). They act as toggles — one overrides the other. You can switch back and forth between protected and public as often as you like.

Take, for example, a class Student that describes the salient features of a college student. This class has the following public member functions:

  • addGrade(int nHours, double dGrade) — add a grade to the student.
  • grade() — return the student’s grade-point average (GPA).
  • hours() — return the number of semester hours toward graduation.

The remaining members of Student should be declared protected to keep prying expressions out of his business.

The following SimpleStudent program defines such a Student class and includes a simple main() that exercises the functions:

  //
//  SimpleStudent - this program demonstrates how the
//                  protected keyword is used to protect
//                  key internal members
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

class Student
{
  protected:
    double dGrade;         // the student's GPA
    int    nSemesterHours;

  public:
    // init() - initialize the student to a legal state
    void init()
    {
        dGrade = 0.0;
        nSemesterHours = 0;
    }

    // getGrade() - return the current grade
    double getGrade()
    {
        return dGrade;
    }

    // getHours() - get the class hours towards graduation
    int getHours()
    {
        return nSemesterHours;
    }

    // addGrade - add a grade to the GPA and total hours
    double addGrade(double dNewGrade, int nHours)
    {
        double dWtdHrs = dGrade * nSemesterHours;
        dWtdHrs += dNewGrade * nHours;
        nSemesterHours += nHours;
        dGrade = dWtdHrs / nSemesterHours;
        return dGrade;
    }
};

int main(int nNumberofArgs, char* pszArgs[])
{
    // create a student and initialize it
    Student s;
    s.init();

    // add the grades for three classes
    s.addGrade(3.0, 3);  // a B
    s.addGrade(4.0, 3);  // an A
    s.addGrade(2.0, 3);  // a C (average should be a B)

    // now print the results
    cout << "Total # hours = " << s.getHours()
         << ", GPA = " << s.getGrade()
         << endl;

    // wait until user is ready before terminating program
    // to allow the user to see the program results
    cout << "Press Enter to continue..." << endl;
    cin.ignore(10, ' '),
    cin.get();
    return 0;
}

This Student protects its members dGrade and nSemesterHours. Outside functions can’t surreptitiously set their own GPA high by slipping in the following:

  void MyFunction(Student* pS)
{
    // set my grade to A+
    pS->dGrade = 3.9;   // generates a compiler error
}

This assignment generates a compiler error.

tip.eps You can start with either the protected or public members; it doesn’t matter. In fact, you can switch back and forth as often as you like.

Any function can read a student’s GPA through the function getGrade(). This is known as an access function. However, although external functions can read a value, they cannot change the value via this access function.

technicalstuff.eps An access function is also known as a getter function (as in “get the value”). A function that sets the value is also known as a setter function. They are also known as accessors and mutators, respectively.

The main() function in this program creates a Student object s. It cannot initialize s to some legal state since the data members are protected. Fortunately, the Student class has provided an init() function for main() to call that initializes the data members to their proper starting state.

After initializing s, main() calls addGrade() to add three different courses and prints out the results using the access member functions. The results appear as follows:

  Total # hours = 9, GPA = 3
Press Enter to continue …

So what?

So what’s the big deal? “Okay,” you say, “I see the point about not letting other functions set the GPA to some arbitrary value, but is that it?” No. A finer point lies behind this loose coupling. I chose to implement the algorithms for calculating the GPA as simply as I possibly could. With no more than five minutes’ thought, I can imagine at least three different ways I could have chosen to store the grades and semester hours internally, each with their own advantages and disadvantages.

For example, I could save each grade — along with the number of semester hours — in an internal array. This would allow the student to review the grades that are going into his GPA.

The point is that the application programmer shouldn’t care. As long as the member functions getGrade() and getHours() calculate the GPA and total number of semester hours accurately, no application is going to care.

Now suppose the school changes the rules for how to calculate the GPA. Suppose, for example, that it declares certain classes to be Pass/Fail, meaning that you get credit toward graduation but the grade in the class doesn’t go into the GPA calculation. This may require a total rewrite of the Student class. That, in turn, would require modification to any functions that rely upon the way that the information is stored internally — that is, any functions that have access to the protected members. However, functions that limit themselves to the public members are unaffected by the change.

That is the true advantage of loose coupling: tolerance to change.

Who Needs Friends, Anyway?

Occasionally, you need to give a non-member function access to the protected members of a class. You can do this by declaring the function to be a friend — which means you don’t have to expose the protected members to everyone by declaring them public.

It’s like giving your neighbor a key to check on your house during your vacation. Giving non-family members keys to the house is not normally a good idea, but it beats the alternative of leaving the house unlocked.

The friend declaration appears in the class that contains the protected member. The friend declaration consists of the keyword friend followed by a prototype declaration. In the following example, the initialize() function is declared as a non-member. However, initialize() clearly needs access to all the data members of the class, protected or not:

  class Student
{
    friend void initialize(Student*);
  protected:
    double dGrade;         // the student's GPA
    int    nSemesterHours;

  public:
    double grade();
    int hours();
    double addGrade(double dNewGrade, int nHours);
};

void initialize(Student* pS)
{
    pS->dGrade = 0.0;
    pS->nSemesterHours = 0;
}

A single function can be declared to be a friend of two different classes at the same time. Although this may seem convenient, it tends to bind the two classes together. However, sometimes the classes are bound together by their very nature, as in the following teacher-student example:

  class Student;   // forward declaration
class Teacher
{
    friend void registration(Teacher*, Student*);
  protected:
    int noStudents;
    Student *pList[128];

  public:
    void assignGrades();
};

class Student
{
    friend void registration(Teacher*, Student*);
  protected:
    Teacher *pTeacher;
    int nSemesterHours;
    double dGrade;
};

In this example, the registration() function can reach into both the Student object to set the pTeacher pointer and into the Teacher object to add to the teacher’s list of students.

tip.eps Notice how the class Student first appears by itself with no body. This is called a forward declaration and declares the intention of the programmer to define a class Student somewhere within the module. This is a little bit like the prototype declaration for a function described in Chapter 11. This is generally necessary only when two or more classes reference each other; in this case, Teacher contains a reference to Student and Student to Teacher.

Without the forward declaration to Student, the declaration within Teacher of Student *pList[100] generates a compiler error because the compiler doesn’t yet know what a Student is. Swap the order of the definitions, and the declaration Teacher *pTeacher within Student generates a compiler error because Teacher has not been defined yet.

The forward declaration solves the problem by telling the compiler to be patient — a definition for this new class is coming very soon.

A member of one class can be declared a friend of another class:

  class Student;

class Teacher
{
    // ...other members...
  public:
    void assignGrade(Student*, int nHours, double dGrade);
};

class Student
{
    friend void Teacher::assignGrade(Student*,
                                     int, double);
    // ...other members...
};

An entire class can be declared a friend of another class. This has the effect of making every member function of the class a friend. For example:

  class Student;

class Teacher
{
  protected:
    int noStudents;
    Student* pList[128];

  public:
    void assignGrade(Student*, int nHours, double dGrade);
};

class Student
{
    friend class Teacher;

    // ...other members...
};

Now every member of Teacher can access the protected members of Student (but not the other way around). Declaring one class to be a friend of another binds the classes together inseparably.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.204.0