Chapter 3 introduced access specifiers public
and private
. A base class’s public
members are accessible within its body and anywhere that the program has a handle (i.e., a name, reference or pointer) to an object of that class or one of its derived classes. A base class’s private
members are accessible only within its body and to the friend
s of that base class. In this section, we introduce the access specifier protected.
Using protected
access offers an intermediate level of protection between public
and private
access. To enable class BasePlusCommissionEmployee
to directly access CommissionEmployee
data members firstName
, lastName
, socialSecurityNumber
, grossSales
and commissionRate
, we can declare those members as protected
in the base class. A base class’s protected
members can be accessed within the body of that base class, by members and friend
s of that base class, and by members and friend
s of any classes derived from that base class.
Class CommissionEmployee
(Fig. 11.12) now declares data members firstName
, lastName
, socialSecurityNumber
, grossSales
and commissionRate
as protected
(lines 31–36) rather than private
. The member-function implementations are identical to those in Fig. 11.5, so CommissionEmployee.cpp
is not shown here.
1 // Fig. 11.12: CommissionEmployee.h
2 // CommissionEmployee class definition with protected data.
3 #ifndef COMMISSION_H
4 #define COMMISSION_H
5
6 #include <string> // C++ standard string class
7
8 class CommissionEmployee
9 {
10 public:
11 CommissionEmployee( const std::string &, const std::string &,
12 const std::string &, double = 0.0, double = 0.0 );
13
14 void setFirstName( const std::string & ); // set first name
15 std::string getFirstName() const; // return first name
16
17 void setLastName( const std::string & ); // set last name
18 std::string getLastName() const; // return last name
19
20 void setSocialSecurityNumber( const std::string & ); // set SSN
21 std::string getSocialSecurityNumber() const; // return SSN
22
23 void setGrossSales( double ); // set gross sales amount
24 double getGrossSales() const; // return gross sales amount
25
26 void setCommissionRate( double ); // set commission rate
27 double getCommissionRate() const; // return commission rate
28
29 double earnings() const; // calculate earnings
30 void print() const; // print CommissionEmployee object
31 protected:
32 std::string firstName;
33 std::string lastName;
34 std::string socialSecurityNumber;
35 double grossSales; // gross weekly sales
36 double commissionRate; // commission percentage
37 }; // end class CommissionEmployee
38
39 #endif
The definition of class BasePlusCommissionEmployee
from Figs. 11.10–11.11 remains unchanged, so we do not show it again here. Now that BasePlusCommissionEmployee
inherits from the updated class CommissionEmployee
(Fig. 11.12), BasePlusCommissionEmployee
objects can access inherited data members that are declared protected
in class CommissionEmployee
(i.e., data members firstName
, lastName
, socialSecurityNumber
, grossSales
and commissionRate
). As a result, the compiler does not generate errors when compiling the BasePlusCommissionEmployee earnings
and print
member-function definitions in Fig. 11.11 (lines 34–38 and 41–49, respectively). This shows the special privileges that a derived class is granted to access protected
base-class data members. Objects of a derived class also can access protected
members in any of that derived class’s indirect base classes.
Class BasePlusCommissionEmployee
does not inherit class CommissionEmployee
’s constructor. However, class BasePlusCommissionEmployee
’s constructor (Fig. 11.11, lines 9–16) calls class CommissionEmployee
’s constructor explicitly with member initializer syntax (line 13). Recall that BasePlusCommissionEmployee
’s constructor must explicitly call the constructor of class CommissionEmployee
, because CommissionEmployee
does not contain a default constructor that could be invoked implicitly.
To test the updated class hierarchy, we reused the test program from Fig. 11.9. As shown in Fig. 11.13, the output is identical to that of Fig. 11.9. We created the first class BasePlusCommissionEmployee
without using inheritance and created this version of BasePlusCommissionEmployee
using inheritance; however, both classes provide the same functionality. The code for class BasePlusCommissionEmployee
(i.e., the header and implementation files), which is 74 lines, is considerably shorter than the code for the noninherited version of the class, which is 161 lines, because the inherited version absorbs part of its functionality from CommissionEmployee
, whereas the noninherited version does not absorb any functionality. Also, there is now only one copy of the CommissionEmployee
functionality declared and defined in class CommissionEmployee
. This makes the source code easier to maintain, modify and debug, because the source code related to a CommissionEmployee
exists only in the files CommissionEmployee.h
and CommissionEmployee.cpp
.
Employee information obtained by get functions:
First name is Bob
Last name is Lewis
Social security number is 333-33-3333
Gross sales is 5000.00
Commission rate is 0.04
Base salary is 300.00
Updated employee information output by print function:
base-salaried commission employee: Bob Lewis
social security number: 333-33-3333
gross sales: 5000.00
commission rate: 0.04
base salary: 1000.00
Employee's earnings: $1200.00
In this example, we declared base-class data members as protected
, so derived classes can modify the data directly. Inheriting protected
data members slightly improves performance, because we can directly access the members without incurring the overhead of calls to set or get member functions.
Software Engineering Observation 11.3
In most cases, it’s better to use private data members to encourage proper software engineering, and leave code optimization issues to the compiler. Your code will be easier to maintain, modify and debug.
Using protected
data members creates two serious problems. First, the derived-class object does not have to use a member function to set the value of the base class’s protected
data member. An invalid value can easily be assigned to the protected
data member, thus leaving the object in an inconsistent state—e.g., with CommissionEmployee
’s data member grossSales
declared as protected
, a derived-class object can assign a negative value to grossSales
. The second problem with using protected
data members is that derived-class member functions are more likely to be written so that they depend on the base-class implementation. Derived classes should depend only on the base-class services (i.e., non-private
member functions) and not on the base-class implementation. With protected
data members in the base class, if the base-class implementation changes, we may need to modify all derived classes of that base class. For example, if for some reason we were to change the names of data members firstName
and lastName
to first
and last
, then we’d have to do so for all occurrences in which a derived class references these base-class data members directly. Such software is said to be fragile or brittle, because a small change in the base class can “break” derived-class implementation. You should be able to change the base-class implementation while still providing the same services to derived classes. Of course, if the base-class services change, we must reimplement our derived classes—good object-oriented design attempts to prevent this.
Software Engineering Observation 11.4
It’s appropriate to use the protected access specifier when a base class should provide a service (i.e., a non-private member function) only to its derived classes and friends.
Software Engineering Observation 11.5
Declaring base-class data members private (as opposed to declaring them protected) enables you to change the base-class implementation without having to change derived-class implementations.