Protecting Members: Do Not Disturb - Introduction to Classes - C++ For Dummies (2014)

C++ For Dummies (2014)

Part III

Introduction to Classes

Chapter 14

Protecting Members: Do Not Disturb

In This Chapter

arrow Declaring members protected

arrow Accessing protected members from within the class

arrow 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.

Protecting Members

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.

image 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.

Why you need protected members

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 non-object oriented 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.

Discovering how protected members work

By default, the members of a class are protected, which means they are not accessible by nonmembers of the class. Adding the keyword public to a class makes subsequent members public, which means that they are accessible by nonmember functions. Adding the keyword protectedmakes subsequent members of the class protected. 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, double 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
double grade() { return gpa;}

// hours - return the number of semester hours
int hours() { return semesterHours; }
// addCourse - add a course to the student's record
double addCourse(int hours, double grade);

// the following members are off-limits to others
protected:
int semesterHours; // hours earned toward graduation
double 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
double gpa = s.grade();// <- this public function reads
// a copy of the value, but you
return 0; // can't change it from here
}

The application’s attempt to change the value of gpa is flagged with a compiler error.

image 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.

Making an Argument for Using Protected Members

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.

Protecting the internal state of the class

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 directly.

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
double grade() { return gpa; }
// here we allow the grade to be changed
double grade(double newGPA)
{
double 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
double gpa;
};

The addition of the member function grade(double) 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.

Using a class with a limited interface

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.

Giving Non-member Functions Access to Protected Members

Occasionally, you want a non-member 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
double 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
double gpa;
};

void registration(Teacher& t, Student& s)
{
// initialize the Student object
s.semesterHours = 0;
s.gpa = 0;

// if there's room...
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.

image 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
double gpa;
};
void Teacher::assignGrades()
{
// can access protected members of Teacher from here
}

Unlike in the non-member 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
double 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.