11.3 Relationship between Base and Derived Classes

In this section, we use an inheritance hierarchy containing types of employees in a company’s payroll application to discuss the relationship between a base class and a derived class. Commission employees (who will be represented as objects of a base class) are paid a percentage of their sales, while base-salaried commission employees (who will be represented as objects of a derived class) receive a base salary plus a percentage of their sales. We divide our discussion of the relationship between commission employees and base-salaried commission employees into a carefully paced series of examples.

11.3.1 Creating and Using a CommissionEmployee Class

Let’s examine CommissionEmployee’s class definition (Figs. 11.411.5). The CommissionEmployee header (Fig. 11.4) specifies class CommissionEmployee’s public services, which include a constructor (lines 10–11) and member functions earnings (line 28) and toString (line 29). Lines 13–26 declare public get and set functions that manipulate the class’s data members (declared in lines 31–35) firstName, lastName, socialSecurityNumber, grossSales and commissionRate. Member functions setGrossSales (defined in lines 46–52 of Fig. 11.5) and setCommissionRate (defined in lines 58–64 of Fig. 11.5), for example, validate their arguments before assigning the values to data members grossSales and commissionRate, respectively.

Fig. 11.4 CommissionEmployee class definition represents a commission employee.

Alternate View

 1   // Fig. 11.4: CommissionEmployee.h
 2   // CommissionEmployee class definition represents a commission employee.
 3   #ifndef COMMISSION_H
 4   #define COMMISSION_H
 5
 6   #include <string> // C++ standard string class
 7
 8   class CommissionEmployee {
 9   public:
10      CommissionEmployee(const std::string&, const std::string&,
11         const std::string&, double = 0.0, double = 0.0);       
12
13      void setFirstName(const std::string&); // set first name
14      std::string getFirstName() const; // return first name
15
16      void setLastName(const std::string&); // set last name
17      std::string getLastName() const; // return last name
18
19      void setSocialSecurityNumber(const std::string&); // set SSN
20      std::string getSocialSecurityNumber() const; // return SSN
21
22      void setGrossSales(double); // set gross sales amount
23      double getGrossSales() const; // return gross sales amount
24
25      void setCommissionRate(double); // set commission rate (percentage)
26      double getCommissionRate() const; // return commission rate
27
28      double earnings() const; // calculate earnings
29      std::string toString() const; // create string representation
30   private:
31      std::string firstName;
32      std::string lastName;
33      std::string socialSecurityNumber;
34      double grossSales; // gross weekly sales
35      double commissionRate; // commission percentage
36   };
37
38   #endif

Fig. 11.5 Implementation file for CommissionEmployee class that represents an employee who is paid a percentage of gross sales.

Alternate View

 1   // Fig. 11.5: CommissionEmployee.cpp
 2   // Class CommissionEmployee member-function definitions.
 3   #include <iomanip>
 4   #include <stdexcept>
 5   #include <sstream>
 6   #include "CommissionEmployee.h" // CommissionEmployee class definition
 7   using namespace std;
 8
 9   // constructor                                                        
10   CommissionEmployee::CommissionEmployee(const string& first,           
11      const string& last, const string& ssn, double sales, double rate) {
12      firstName = first; // should validate                              
13      lastName = last; // should validate                                
14      socialSecurityNumber = ssn; // should validate                     
15      setGrossSales(sales); // validate and store gross sales            
16      setCommissionRate(rate); // validate and store commission rate     
17   }                                                                     
18
19   // set first name
20   void CommissionEmployee::setFirstName(const string& first) {
21      firstName = first; // should validate
22   }
23
24   // return first name
25   string CommissionEmployee::getFirstName() const {return firstName;}
26
27   // set last name
28   void CommissionEmployee::setLastName(const string& last) {
29      lastName = last; // should validate
30   }
31
32   // return last name
33   string CommissionEmployee::getLastName() const {return lastName;}
34
35   // set social security number
36   void CommissionEmployee::setSocialSecurityNumber(const string& ssn) {
37      socialSecurityNumber = ssn; // should validate
38   }
39
40   // return social security number
41   string CommissionEmployee::getSocialSecurityNumber() const {
42      return socialSecurityNumber;
43   }
44
45   // set gross sales amount
46   void CommissionEmployee::setGrossSales(double sales) {
47      if (sales < 0.0) {
48         throw invalid_argument("Gross sales must be >= 0.0");
49      }
50
51      grossSales = sales;
52   }
53
54   // return gross sales amount
55   double CommissionEmployee::getGrossSales() const {return grossSales;}
56
57   // set commission rate
58   void CommissionEmployee::setCommissionRate(double rate) {
59      if (rate <= 0.0 || rate >= 1.0) {
60         throw invalid_argument("Commission rate must be > 0.0 and < 1.0");
61      }
62
63      commissionRate = rate;
64   }
65
66   // return commission rate
67   double CommissionEmployee::getCommissionRate() const {
68      return commissionRate;
69   }
71   // calculate earnings                        
72   double CommissionEmployee::earnings() const {
73      return commissionRate * grossSales; }     
74   }                                            
75
76   // return string representation of CommissionEmployee object        
77   string CommissionEmployee::toString() const {                       
78      ostringstream output;                                            
79      output << fixed << setprecision(2); // two digits of precision   
80      output << "commission employee: " << firstName << " " << lastName
81         << "
social security number: " << socialSecurityNumber       
82         << "
gross sales: " << grossSales                            
83         << "
commission rate: " << commissionRate;                   
84      return output.str();                                             
85   }                                                                   

CommissionEmployee Constructor

The CommissionEmployee constructor definition purposely does not use member-initializer syntax in the first several examples of this section. This will enable us to demonstrate how private and protected specifiers affect member access in derived classes. As shown in Fig. 11.5, lines 12–14, we assign values to data members firstName, lastName and socialSecurityNumber in the constructor body. Later in this section, we’ll return to using member-initializer lists in the constructors.

We do not validate the values of the constructor’s arguments first, last and ssn before assigning them to the corresponding data members. We certainly could validate the first and last names—perhaps by ensuring that they’re of a reasonable length. Similarly, a social security number could be validated to ensure that it contains nine digits, with or without dashes (e.g., 123-45-6789 or 123456789).

CommissionEmployee Member Functions earnings and toString

Member function earnings (lines 72–74) calculates a CommissionEmployee’s earnings. Line 73 multiplies the commissionRate by the grossSales and returns the result. Member function toString (lines 77–85) displays the values of a CommissionEmployee object’s data members.

Testing Class CommissionEmployee

Figure 11.6 tests class CommissionEmployee. Line 10 instantiates CommissionEmployee object employee and invokes the constructor to initialize the object with "Sue" as the first name, "Jones" as the last name, "222-22-2222" as the social security number, 10000 as the gross sales amount and .06 as the commission rate. Lines 14–20 use employee’s get functions to display the values of its data members. Lines 22–23 invoke the object’s member functions setGrossSales and setCommissionRate to change the values of data members grossSales and commissionRate, respectively. Lines 24–25 then call employee’s toString member function to get and output the updated CommissionEmployee information. Finally, line 28 displays the CommissionEmployee’s earnings, calculated by the object’s earnings member function using the updated values of data members grossSales and commissionRate.

Fig. 11.6 CommissionEmployee class test program.

Alternate View

 1   // Fig. 11.6: fig11_06.cpp
 2   // CommissionEmployee class test program.
 3   #include <iostream>
 4   #include <iomanip>
 5   #include "CommissionEmployee.h" // CommissionEmployee class definition
 6   using namespace std;
 7
 8   int main() {
 9      // instantiate a CommissionEmployee object
10      CommissionEmployee employee{"Sue", "Jones", "222-22-2222", 10000, .06};
11
12     // get commission employee data
13     cout << fixed << setprecision(2); // set floating-point formatting
14     cout << "Employee information obtained by get functions: 
"
15        << "
First name is " << employee.getFirstName()
16        << "
Last name is " << employee.getLastName()
17        << "
Social security number is "
18        << employee.getSocialSecurityNumber()
19        << "
Gross sales is " << employee.getGrossSales()
20        << "
Commission rate is " <<  employee.getCommissionRate() << endl;
21
22      employee.setGrossSales(8000); // set gross sales      
23      employee.setCommissionRate(.1); // set commission rate
24      cout << "
Updated employee information from function toString: 

"
25         << employee.toString();
26
27      // display the employee's earnings
28      cout << "

Employee's earnings: $" << employee.earnings() << endl;
29   }

Employee information obtained by get functions:

First name is Sue
Last name is Jones
Social security number is 222-22-2222
Gross sales is 10000.00
Commission rate is 0.06

Updated employee information from function toString:

commission employee: Sue Jones
social security number: 222-22-2222
gross sales: 8000.00
commission rate: 0.10

Employee's earnings: $800.00

11.3.2 Creating a BasePlusCommissionEmployee Class Without Using Inheritance

We now discuss the second part of our introduction to inheritance by creating and testing a completely new and independent class BasePlusCommissionEmployee (Figs. 11.711.8), which contains a first name, last name, social security number, gross sales amount, commission rate and base salary.

Fig. 11.7 BasePlusCommissionEmployee class header.

Alternate View

 1   // Fig. 11.7: BasePlusCommissionEmployee.h
 2   // BasePlusCommissionEmployee class definition represents an employee
 3   // that receives a base salary in addition to commission.
 4   #ifndef BASEPLUS_H
 5   #define BASEPLUS_H
 6
 7   #include <string> // C++ standard string class
 8
 9   class BasePlusCommissionEmployee {
10   public:
11      BasePlusCommissionEmployee(const std::string&, const std::string&,
12         const std::string&, double = 0.0, 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      void setBaseSalary(double); // set base salary     
30      double getBaseSalary() const; // return base salary
31
32      double earnings() const; // calculate earnings
33      std::string toString() const; // create string representation
34   private:
35      std::string firstName;
36      std::string lastName;
37      std::string socialSecurityNumber;
38      double grossSales; // gross weekly sales
39      double commissionRate; // commission percentage
40      double baseSalary; // base salary
41   };
42
43   #endif


Fig. 11.8 BasePlusCommissionEmployee class represents an employee who receives a base salary in addition to a commission.

Alternate View

 1    // Fig. 11.8: BasePlusCommissionEmployee.cpp
 2    // Class BasePlusCommissionEmployee member-function definitions.
 3    #include <iomanip>
 4    #include <stdexcept>
 5    #include <sstream>
 6    #include "BasePlusCommissionEmployee.h"
 7    using namespace std;
 8
 9    // constructor
10    BasePlusCommissionEmployee::BasePlusCommissionEmployee(
11       const string& first, const string& last, const string& ssn,
12       double sales, double rate, double salary) {
13       firstName = first; // should validate
14       lastName = last; // should validate
15       socialSecurityNumber = ssn; // should validate
16       setGrossSales(sales); // validate and store gross sales
17       setCommissionRate(rate); // validate and store commission rate
18       setBaseSalary(salary); // validate and store base salary
19    }
20
21    // set first name
22    void BasePlusCommissionEmployee::setFirstName(const string& first) {
23       firstName = first; // should validate
24    }
25
26    // return first name
27    string BasePlusCommissionEmployee::getFirstName() const {
28       return firstName;
29    }
30
31    // set last name
32    void BasePlusCommissionEmployee::setLastName(const string& last) {
33       lastName = last; // should validate
34    }
35
36    // return last name
37    string BasePlusCommissionEmployee::getLastName() const {return lastName;}
38
39    // set social security number
40    void BasePlusCommissionEmployee::setSocialSecurityNumber(
41       const string& ssn) {
42       socialSecurityNumber = ssn; // should validate
43    }
44
45    // return social security number
46    string BasePlusCommissionEmployee::getSocialSecurityNumber() const {
47       return socialSecurityNumber;
48    }
49
50    // set gross sales amount
51    void BasePlusCommissionEmployee::setGrossSales(double sales) {
52       if (sales < 0.0) {
53          throw invalid_argument("Gross sales must be >= 0.0");
54       }
55
56       grossSales = sales;
57    }
58
59    // return gross sales amount
60    double BasePlusCommissionEmployee::getGrossSales() const {
61       return grossSales;
62    }
63
64    // set commission rate
65    void BasePlusCommissionEmployee::setCommissionRate(double rate) {
66       if (rate <= 0.0 || rate >= 1.0) {
67          throw invalid_argument("Commission rate must be > 0.0 and < 1.0");
68       }
69
70       commissionRate = rate;
71    }
72
73    // return commission rate
74    double BasePlusCommissionEmployee::getCommissionRate() const {
75       return commissionRate;
76    }
77
78    // set base salary                                             
79    void BasePlusCommissionEmployee::setBaseSalary(double salary) {
80       if (salary < 0.0) {                                         
81          throw invalid_argument("Salary must be >= 0.0");         
82       }                                                           
83                                                                   
84       baseSalary = salary;                                        
85    }                                                              
86
87    // return base salary                                     
88    double BasePlusCommissionEmployee::getBaseSalary() const {
89       return baseSalary; }                                   
90    }                                                         
91
92    // calculate earnings                                  
93    double BasePlusCommissionEmployee::earnings() const {  
94       return baseSalary + (commissionRate * grossSales); }
95    }                                                      
96
97    // return string representation of BasePlusCommissionEmployee object
98    string BasePlusCommissionEmployee::toString() const {
99       ostringstream output;
100      output << fixed << setprecision(2); // two digits of precision
101      output << "base-salaried commission employee: " << firstName << ' '
102         << lastName << "
social security number: " << socialSecurityNumber
103         << "
gross sales: " << grossSales
104         << "
commission rate: " << commissionRate
105         << "
base salary: " << baseSalary;
106      return output.str();
107   }

Defining Class BasePlusCommissionEmployee

The BasePlusCommissionEmployee header (Fig. 11.7) specifies class BasePlusCommissionEmployee’s public services, which include the BasePlusCommissionEmployee constructor (lines 11–12) and member functions earnings (line 32) and toString (line 33). Lines 14–30 declare public get and set functions for the class’s private data members (declared in lines 35–40) firstName, lastName, socialSecurityNumber, grossSales, commissionRate and baseSalary. These variables and member functions encapsulate all the necessary features of a base-salaried commission employee. Note the similarity between this class and class CommissionEmployee (Figs. 11.411.5)—in this example, we do not yet exploit that similarity.

Class BasePlusCommissionEmployee’s earnings member function (defined in lines 93–95 of Fig. 11.8) computes the earnings of a base-salaried commission employee. Line 94 returns the result of adding the employee’s base salary to the product of the commission rate and the employee’s gross sales.

Testing Class BasePlusCommissionEmployee

Figure 11.9 tests class BasePlusCommissionEmployee. Lines 10–11 instantiate object employee of class BasePlusCommissionEmployee, passing "Bob", "Lewis", "333-33-3333", 5000, .04 and 300 to the constructor as the first name, last name, social security number, gross sales, commission rate and base salary, respectively. Lines 15–22 use BasePlusCommissionEmployee’s get functions to retrieve the values of the object’s data members for output. Line 23 invokes the object’s setBaseSalary member function to change the base salary. Member function setBaseSalary (Fig. 11.8, lines 79–85) ensures that data member baseSalary is not assigned a negative value, because an employee’s base salary cannot be negative. Lines 24–25 of Fig. 11.9 invoke the object’s toString member function to get the updated BasePlusCommissionEmployee’s information, and line 28 calls member function earnings to display the BasePlusCommissionEmployee’s earnings.

Fig. 11.9 BasePlusCommissionEmployee class test program.

Alternate View

 1   // Fig. 11.9: fig11_09.cpp
 2   // BasePlusCommissionEmployee class test program.
 3   #include <iostream>
 4   #include <iomanip>
 5   #include "BasePlusCommissionEmployee.h"
 6   using namespace std;
 7
 8   int main() {
 9      // instantiate BasePlusCommissionEmployee object
10      BasePlusCommissionEmployee employee{"Bob", "Lewis", "333-33-3333",
11         5000, .04, 300};
12
13      // get commission employee data
14      cout << fixed << setprecision(2); // set floating-point formatting
15      cout << "Employee information obtained by get functions: 
"
16         << "
First name is " << employee.getFirstName()
17         << "
Last name is " << employee.getLastName()
18         << "
Social security number is "
19         << employee.getSocialSecurityNumber()
20         << "
Gross sales is " << employee.getGrossSales()
21         << "
Commission rate is " << employee.getCommissionRate()
22         << "
Base salary is " << employee.getBaseSalary() << endl;
23     employee.setBaseSalary(1000); // set base salary
24     cout << "
Updated employee information from function toString: 

"
25        << employee.toString();
26
27     // display the employee's earnings
28     cout << "

Employee's earnings: $" << employee.earnings() << endl;
29   }

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 from function toString:

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

Exploring the Similarities Between Class BasePlusCommissionEmployee and Class CommissionEmployee

Most of the code for class BasePlusCommissionEmployee (Figs. 11.711.8) is similar, if not identical, to the code for class CommissionEmployee (Figs. 11.411.5). For example, in class BasePlusCommissionEmployee, private data members firstName and lastName and member functions setFirstName, getFirstName, setLastName and getLastName are identical to those of class CommissionEmployee. Classes CommissionEmployee and BasePlusCommissionEmployee also both contain private data members socialSecurityNumber, commissionRate and grossSales, as well as get and set functions to manipulate these members. In addition, the BasePlusCommissionEmployee constructor is almost identical to that of class CommissionEmployee, except that BasePlusCommissionEmployee’s constructor also sets the baseSalary. The other additions to class BasePlusCommissionEmployee are private data member baseSalary and member functions setBaseSalary and getBaseSalary. Class BasePlusCommissionEmployee’s toString member function is nearly identical to that of class CommissionEmployee, except that BasePlusCommissionEmployee’s toString also outputs the value of data member baseSalary.

We literally copied code from class CommissionEmployee and pasted it into class BasePlusCommissionEmployee, then modified class BasePlusCommissionEmployee to include a base salary and member functions that manipulate the base salary. This copy-and-paste approach is error prone and time consuming.

Software Engineering Observation 11.1

Copying and pasting code from one class to another can spread many physical copies of the same code and can spread errors throughout a system, creating a code-maintenance nightmare. To avoid duplicating code (and possibly errors), use inheritance, rather than the “copy-and-paste” approach, in situations where you want one class to “absorb” the data members and member functions of another class.

 

Software Engineering Observation 11.2

With inheritance, the common data members and member functions of all the classes in the hierarchy are declared in a base class. When changes are required for these common features, you need to make the changes only in the base class—derived classes then inherit the changes. Without inheritance, changes would need to be made to all the source-code files that contain a copy of the code in question.

11.3.3 Creating a CommissionEmployee–BasePlusCommissionEmployee Inheritance Hierarchy

Now we create and test a new BasePlusCommissionEmployee class (Figs. 11.1011.11) that derives from class CommissionEmployee (Figs. 11.411.5). In this example, a BasePlusCommissionEmployee object is a CommissionEmployee (because inheritance passes on the capabilities of class CommissionEmployee), but class BasePlusCommissionEmployee also has data member baseSalary (Fig. 11.10, line 21). The colon (:) in line 10 of the class definition indicates inheritance. Keyword public indicates the type of inheritance. As a derived class (formed with public inheritance), BasePlusCommissionEmployee inherits all the members of class CommissionEmployee, except for the constructor—each class provides its own constructors that are specific to the class. (Destructors, too, are not inherited.) Thus, the public services of BasePlusCommissionEmployee include its constructor (lines 12–13) and the public member functions inherited from class CommissionEmployeealthough we cannot see these inherited member functions in BasePlusCommissionEmployee’s source code, they’re nevertheless a part of derived-class BasePlusCommissionEmployee. The derived class’s public services also include member functions setBaseSalary, getBaseSalary, earnings and toString (lines 15–19).

Figure 11.11 shows BasePlusCommissionEmployee’s member-function implementations. The constructor (lines 10–16) introduces base-class initializer syntax (line 14), which uses a member initializer to pass arguments to the base-class (CommissionEmployee)

Fig. 11.10 BasePlusCommissionEmployee class definition indicating inheritance relationship with class CommissionEmployee.

Alternate View

 1   // Fig. 11.10: BasePlusCommissionEmployee.h
 2   // BasePlusCommissionEmployee class derived from class
 3   // CommissionEmployee.
 4   #ifndef BASEPLUS_H
 5   #define BASEPLUS_H
 6
 7   #include <string> // C++ standard string class
 8   #include "CommissionEmployee.h" // CommissionEmployee class declaration
 9
10   class BasePlusCommissionEmployee : public CommissionEmployee {
11   public:
12      BasePlusCommissionEmployee(const std::string&, const std::string&,
13         const std::string&, double = 0.0, double = 0.0, double = 0.0);
14
15      void setBaseSalary(double); // set base salary
16      double getBaseSalary() const; // return base salary
17
18      double earnings() const; // calculate earnings
19      std::string toString() const; // create string representation
20   private:
21      double baseSalary; // base salary
22   };
23
24   #endif

Fig. 11.11 BasePlusCommissionEmployee implementation file: private base-class data cannot be accessed from derived class.

Alternate View

 1   // Fig. 11.11: BasePlusCommissionEmployee.cpp
 2   // Class BasePlusCommissionEmployee member-function definitions.
 3   #include <iomanip>
 4   #include <sstream>
 5   #include <stdexcept>
 6   #include "BasePlusCommissionEmployee.h"
 7   using namespace std;
 8
 9   // constructor
10   BasePlusCommissionEmployee::BasePlusCommissionEmployee(
11      const string& first, const string& last, const string& ssn,
12      double sales, double rate, double salary)
13      // explicitly call base-class constructor
14      : CommissionEmployee(first, last, ssn, sales, rate) {
15      setBaseSalary(salary); // validate and store base salary
16   }
17
18   // set base salary
19   void BasePlusCommissionEmployee::setBaseSalary(double salary) {
20      if (salary < 0.0) {
21         throw invalid_argument("Salary must be >= 0.0");
22      }
23
24      baseSalary = salary;
25   }
26
27   // return base salary
28   double BasePlusCommissionEmployee::getBaseSalary() const {
29      return baseSalary;
30   }
31
32   // calculate earnings
33   double BasePlusCommissionEmployee::earnings() const {
34      // derived class cannot access the base class’s private data
35      return baseSalary + (commissionRate * grossSales);          
36   }
37
38   // returns string representation of BasePlusCommissionEmployee object
39   string BasePlusCommissionEmployee::toString() const {
40      ostringstream output;
41      output << fixed << setprecision(2); // two digits of precision
42
43      // derived class cannot access the base class’s private data          
44      output << "base-salaried commission employee: " << firstName << ' '   
45         << lastName << "
social security number: " << socialSecurityNumber
46         << "
gross sales: " << grossSales                                 
47         << "
commission rate: " << commissionRate                         
48         << "
base salary: " << baseSalary;                                
49      return output.str();
50   }

Compilation Errors from the Clang/LLVM Compiler in Xcode 7.2


BasePlusCommissionEmployee.cpp:34:25:
   'commissionRate' is a private member of 'CommissionEmployee'
BasePlusCommissionEmployee.cpp:34:42:
   'grossSales' is a private member of 'CommissionEmployee'
BasePlusCommissionEmployee.cpp:42:55:
   'firstName' is a private member of 'CommissionEmployee'
BasePlusCommissionEmployee.cpp:43:10:
   'lastName' is a private member of 'CommissionEmployee'
BasePlusCommissionEmployee.cpp:43:54:
   'socialSecurityNumber' is a private member of 'CommissionEmployee'
BasePlusCommissionEmployee.cpp:44:31:
   'grossSales' is a private member of 'CommissionEmployee'
BasePlusCommissionEmployee.cpp:45:35:
   'commissionRate' is a private member of 'CommissionEmployee'

constructor. C++ requires that a derived-class constructor call its base-class constructor to initialize the base-class data members that are inherited into the derived class. Line 14 does this by explicitly invoking the CommissionEmployee constructor by name, passing the constructor’s parameters first, last, ssn, sales and rate as arguments to initialize the base-class data members firstName, lastName, socialSecurityNumber, grossSales and commissionRate, respectively. The compiler would issue an error if BasePlusCommissionEmployee’s constructor did not invoke class CommissionEmployee’s constructor explicitly—in this case, C++ attempts to invoke class CommissionEmployee’s default constructor implicitly, but the class does not have such a constructor. Recall from Chapter 3 that the compiler provides a default constructor with no parameters in any class that does not explicitly include a constructor. However, CommissionEmployee does explicitly include a constructor, so a default constructor is not provided.

Common Programming Error 11.1

When a derived-class constructor calls a base-class constructor, the arguments passed to the base-class constructor must be consistent with the number and types of parameters specified in one of the base-class constructors; otherwise, a compilation error occurs.

 

Performance Tip 11.1

In a derived-class constructor, invoking base-class constructors and initializing member objects explicitly in the member initializer list prevents duplicate initialization in which a default constructor is called, then data members are modified again in the derived-class constructor’s body.

Compilation Errors from Accessing Base-Class private Members

The compiler generates errors for line 35 of Fig. 11.11 because base-class CommissionEmployee’s data members commissionRate and grossSales are private—derived-class BasePlusCommissionEmployee’s member functions are not allowed to access base-class CommissionEmployee’s private data. The compiler issues additional errors in lines 44–47 of BasePlusCommissionEmployee’s toString member function for the same reason. As you can see, C++ rigidly enforces restrictions on accessing private data members, so that even a derived class (which is intimately related to its base class) cannot access the base class’s private data.

Preventing the Errors in BasePlusCommissionEmployee

We purposely included the erroneous code in Fig. 11.11 to emphasize that a derived class’s member functions cannot access its base class’s private data. The errors in BasePlusCommissionEmployee could have been prevented by using the get member functions inherited from class CommissionEmployee. For example, line 35 could have invoked getCommissionRate and getGrossSales to access CommissionEmployee’s private data members commissionRate and grossSales, respectively. Similarly, lines 44–47 could have used appropriate get member functions to retrieve the values of the base class’s data members. In the next example, we show how using protected data also allows us to avoid the errors encountered in this example.

Including the Base-Class Header in the Derived-Class Header with #include

Notice that we #include the base class’s header in the derived class’s header (line 8 of Fig. 11.10). This is necessary for three reasons. First, for the derived class to use the base class’s name in line 10, we must tell the compiler that the base class exists—the class definition in CommissionEmployee.h does exactly that.

The second reason is that the compiler uses a class definition to determine the size of an object of that class (as we discussed in Section 9.3). A client program that creates an object of a class #includes the class definition to enable the compiler to reserve the proper amount of memory for the object. When using inheritance, a derived-class object’s size depends on the data members declared explicitly in its class definition and the data members inherited from its direct and indirect base classes. Including the base class’s definition in line 8 of Fig. 11.10 allows the compiler to determine the memory requirements for the base class’s data members that become part of a derived-class object and thus contribute to its total size.

The last reason for line 8 is to allow the compiler to determine whether the derived class uses the base class’s inherited members properly. For example, in the program of Figs. 11.1011.11, the compiler uses the base-class header to determine that the data members being accessed by the derived class are private in the base class. Since these are inaccessible to the derived class, the compiler generates errors. The compiler also uses the base class’s function prototypes to validate function calls made by the derived class to the inherited base-class functions.

Linking Process in an Inheritance Hierarchy

In Section 9.3, we discussed the linking process for creating an executable Time application. In that example, you saw that the client’s object code was linked with the object code for class Time, as well as the object code for any C++ Standard Library classes used either in the client code or in class Time.

The linking process is similar for a program that uses classes in an inheritance hierarchy. The process requires the object code for all classes used in the program and the object code for the direct and indirect base classes of any derived classes used by the program. Suppose a client wants to create an application that uses class BasePlusCommissionEmployee, which is a derived class of CommissionEmployee (we’ll see an example of this in Section 11.3.4). When compiling the client application, the client’s object code must be linked with the object code for classes BasePlusCommissionEmployee and CommissionEmployee, because BasePlusCommissionEmployee inherits member functions from its base-class CommissionEmployee. The code is also linked with the object code for any C++ Standard Library classes used in class CommissionEmployee, class BasePlusCommissionEmployee or the client code. This provides the program with access to the implementations of all of the functionality that the program may use.

11.3.4 CommissionEmployee–BasePlusCommissionEmployee Inheritance Hierarchy Using protected Data

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, including in derived classes. A base class’s private members are accessible only within its body and to the friends 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 friends of that base class, and by members and friends of any classes derived from that base class.

Defining Base-Class CommissionEmployee with protected Data

Class CommissionEmployee (Fig. 11.12) now declares data members firstName, lastName, socialSecurityNumber, grossSales and commissionRate as protected (lines 30–35) rather than private. The member-function implementations are identical to those in Fig. 11.5, so CommissionEmployee.cpp is not shown here.

Fig. 11.12 CommissionEmployee class definition that declares protected data to allow access by derived classes.

Alternate View

 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   public:
10      CommissionEmployee(const std::string&, const std::string&,
11         const std::string&, double = 0.0, double = 0.0);
12
13      void setFirstName(const std::string&); // set first name
14      std::string getFirstName() const; // return first name
15
16      void setLastName(const std::string&); // set last name
17      std::string getLastName() const; // return last name
18
19      void setSocialSecurityNumber(const std::string&); // set SSN
20      std::string getSocialSecurityNumber() const; // return SSN
21
22      void setGrossSales(double); // set gross sales amount
23      double getGrossSales() const; // return gross sales amount
24
25      void setCommissionRate(double); // set commission rate
26      double getCommissionRate() const; // return commission rate
27
28      double earnings() const; // calculate earnings
29      std::string toString() const; // return string representation
30   protected
31      std::string firstName;                         
32      std::string lastName;                          
33      std::string socialSecurityNumber;              
34      double grossSales; // gross weekly sales       
35      double commissionRate; // commission percentage
36   };
37
38   #endif

Class BasePlusCommissionEmployee

The definition of class BasePlusCommissionEmployee from Figs. 11.1011.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 toString member-function definitions in Fig. 11.11 (lines 33–36 and 39–50, 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 10–16) calls class CommissionEmployee’s constructor explicitly with member-initializer syntax (line 14). 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.

Testing the Modified BasePlusCommissionEmployee Class

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.

Fig. 11.13 protected base-class data can be accessed from derived class.

Alternate View

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 from function toString:

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

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 derived-class BasePlusCommissionEmployee (i.e., the header and implementation files) is considerably shorter than the code for the noninherited version of the class, because the inherited version absorbs much 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.

Notes on Using protected Data

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” the 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.

11.3.5 CommissionEmployee–BasePlusCommissionEmployee Inheritance Hierarchy Using private Data

We now reexamine our hierarchy once more, this time using best software engineering practices. Class CommissionEmployee now declares data members firstName, lastName, socialSecurityNumber, grossSales and commissionRate as private, as shown previously in lines 31–35 of Fig. 11.4.

Changes to Class CommissionEmployee’s Member-Function Definitions

In the CommissionEmployee constructor implementation (Fig. 11.14, lines 10–15), we use member initializers (line 12) to set the values of the members firstName, lastName and socialSecurityNumber. Though we do not do so here, the derived-class BasePlusCommissionEmployee (Fig. 11.15) can invoke non-private base-class member functions (setFirstName, getFirstName, setLastName, getLastName, setSocialSecurityNumber and getSocialSecurityNumber) to manipulate these data members, as can any client code of class BasePlusCommissionEmployee (such as main).


Fig. 11.14 CommissionEmployee class implementation file: CommissionEmployee class uses member functions to manipulate its private data.

Alternate View

 1   // Fig. 11.14: CommissionEmployee.cpp
 2   // Class CommissionEmployee member-function definitions.
 3   #include <iomanip>
 4   #include <stdexcept>
 5   #include <sstream>
 6   #include "CommissionEmployee.h" // CommissionEmployee class definition
 7   using namespace std;
 8
 9   // constructor
10   CommissionEmployee::CommissionEmployee (const string &first,
11      const string &last, const string &ssn, double sales, double rate)
12      : firstName(first), lastName(last), socialSecurityNumber(ssn) {
13      setGrossSales(sales); // validate and store gross sales
14      setCommissionRate(rate); // validate and store commission rate
15   }
16
17   // set first name
18   void CommissionEmployee::setFirstName(const string& first) {
19      firstName = first; // should validate
20   }
21
22   // return first name
23   string CommissionEmployee::getFirstName() const {return firstName;}
24
25   // set last name
26   void CommissionEmployee::setLastName(const string& last) {
27      lastName = last; // should validate
28   }
29
30   // return last name
31   string CommissionEmployee::getLastName() const {return lastName;}
32
33   // set social security number
34   void CommissionEmployee::setSocialSecurityNumber(const string& ssn) {
35      socialSecurityNumber = ssn; // should validate
36   }
37
38   // return social security number
39   string CommissionEmployee::getSocialSecurityNumber() const {
40      return socialSecurityNumber;
41   }
42
43   // set gross sales amount
44   void CommissionEmployee::setGrossSales(double sales) {
45      if (sales < 0.0) {
46         throw invalid_argument("Gross sales must be >= 0.0");
47      }
48
49      grossSales = sales;
50   }
51
52   // return gross sales amount
53   double CommissionEmployee::getGrossSales() const {return grossSales;}
54
55   // set commission rate
56   void CommissionEmployee::setCommissionRate(double rate) {
57      if (rate <= 0.0 || rate >= 1.0) {
58         throw invalid_argument("Commission rate must be > 0.0 and < 1.0");
59      }
60
61      commissionRate = rate;
62   }
63
64   // return commission rate
65   double CommissionEmployee::getCommissionRate() const {
66      return commissionRate;
67   }
68
69   // calculate earnings
70   double CommissionEmployee::earnings() const {
71      return getCommissionRate() * getGrossSales() ;
72   }
73
74   // return string representation of CommissionEmployee object
75   string CommissionEmployee::toString() const {
76      ostringstream output;
77      output << fixed << setprecision(2); // two digits of precision
78      output << "commission employee: "
79         << getFirstName() << ' ' << getLastName()
80         << "
social security number: " << getSocialSecurityNumber()
81         << "
gross sales: " << getGrossSales()
82         << "
commission rate: " << getCommissionRate() ;
83      return output.str();
84   }

In the body of the constructor and in the bodies of member functions earnings (Fig. 11.14, lines 70–72) and toString (lines 75–84), we call the class’s set and get member functions to access the class’s private data members. If we decide to change the data member names, the earnings and toString definitions will not require modification—only the definitions of the get and set member functions that directly manipulate the data members will need to change. These changes occur solely within the base class—no changes to the derived class are needed. Localizing the effects of changes like this is a good software engineering practice.

Performance Tip 11.2

Using a member function to access a data member’s value can be slightly slower than accessing the data directly. However, today’s optimizing compilers perform many optimizations implicitly (such as inlining set and get member-function calls). You should write code that adheres to proper software engineering principles, and leave optimization to the compiler. A good rule is, “Do not second-guess the compiler.”

Changes to Class BasePlusCommissionEmployee’s Member-Function Definitions

Class BasePlusCommissionEmployee inherits CommissionEmployee’s public member functions and can access the private base-class members via the inherited member functions. The class’s header remains unchanged from Fig. 11.10. The class has several changes to its member-function implementations (Fig. 11.15) that distinguish it from the previous version of the class (Figs. 11.1011.11). Member functions earnings (Fig. 11.15, lines 32–34) and toString (lines 37–42) each invoke member function getBaseSalary to obtain the base salary value, rather than accessing baseSalary directly. This insulates earnings and toString from potential changes to the implementation of data member baseSalary. For example, if we decide to rename data member baseSalary or change its type, only member functions setBaseSalary and getBaseSalary will need to change.

Fig. 11.15 BasePlusCommissionEmployee class that inherits from class CommissionEmployee but cannot directly access the class’s private data.

Alternate View

 1   // Fig. 11.15: BasePlusCommissionEmployee.cpp
 2   // Class BasePlusCommissionEmployee member-function definitions.
 3   #include <stdexcept>
 4   #include <sstream>
 5   #include "BasePlusCommissionEmployee.h"
 6   using namespace std;
 7
 8   // constructor
 9   BasePlusCommissionEmployee::BasePlusCommissionEmployee(
10      const string& first, const string& last, const string& ssn,
11      double sales, double rate, double salary)
12      // explicitly call base-class constructor
13      : CommissionEmployee(first, last, ssn, sales, rate) {
14      setBaseSalary(salary); // validate and store base salary
15   }
16
17   // set base salary
18   void BasePlusCommissionEmployee::setBaseSalary(double salary) {
19      if (salary < 0.0) {
20         throw invalid_argument("Salary must be >= 0.0");
21      }
22
23      baseSalary = salary;
24   }
25
26   // return base salary
27   double BasePlusCommissionEmployee::getBaseSalary() const {
28      return baseSalary;
29   }
30
31   // calculate earnings
32   double BasePlusCommissionEmployee::earnings() const {
33      return getBaseSalary() + CommissionEmployee::earnings();
34   }
35
36   // return string representation of BasePlusCommissionEmployee object
37   string BasePlusCommissionEmployee::toString() const {
38      ostringstream output;
39      output << "base-salaried " << CommissionEmployee::toString()
40         << "
base salary: " << getBaseSalary();
41      return output.str();
42   }

BasePlusCommissionEmployee Member Function earnings

Class BasePlusCommissionEmployee’s earnings function (Fig. 11.15, lines 32–34) redefines class CommissionEmployee’s earnings member function (Fig. 11.14, lines 70–72) to calculate the earnings of a BasePlusCommissionEmployee. Class BasePlusCommissionEmployee’s version of earnings obtains the portion of the employee’s earnings based on commission alone by calling base-class CommissionEmployee’s earnings function with the expression CommissionEmployee::earnings() as shown in line 33 of Fig. 11.15. BasePlusCommissionEmployee’s earnings function then adds the base salary to this value to calculate the total earnings of the employee. Note the syntax used to invoke a redefined base-class member function from a derived class—place the base-class name and the scope resolution operator (::) before the base-class member-function name. This member-function invocation is a good software engineering practice: Recall from Chapter 9 that, if an object’s member function performs the actions needed by another object, we should call that member function rather than duplicating its code body. By having BasePlusCommissionEmployee’s earnings function invoke CommissionEmployee’s earnings function to calculate part of a BasePlusCommissionEmployee object’s earnings, we avoid duplicating the code and reduce code-maintenance problems.

Common Programming Error 11.2

When a base-class member function is redefined in a derived class, the derived-class version often calls the base-class version to do additional work. Failure to use the :: operator prefixed with the name of the base class when referencing the base class’s member function causes infinite recursion, because the derived-class member function would then call itself.

BasePlusCommissionEmployee Member Function toString

Similarly, BasePlusCommissionEmployee’s toString function (Fig. 11.15, lines 37–42) redefines class CommissionEmployee’s toString function (Fig. 11.14, lines 75–84) to output the appropriate base-salaried commission employee information. The new version displays "base-salaried" followed by the part of a BasePlusCommissionEmployee object’s information returned by calling CommissionEmployee’s toString member function with the qualified name CommissionEmployee::toString() (Fig. 11.15, line 39)—this returns a string containing "commission employee" and the values of class CommissionEmployee’s private data members. BasePlusCommissionEmployee’s toString function then outputs the remainder of a BasePlusCommissionEmployee object’s information (i.e., the value of class BasePlusCommissionEmployee’s base salary preceded by "base salary:").

Testing the Modified Class Hierarchy

Once again, this example uses the BasePlusCommissionEmployee test program from Fig. 11.9 and produces the same output. Although each “base-salaried commission employee” class behaves identically, the version in this example is the best engineered. By using inheritance and by calling member functions that hide the data and ensure consistency, we’ve efficiently and effectively constructed a well-engineered class.

Summary of the CommissionEmployee–BasePlusCommissionEmployee Examples

In this section, you saw an evolutionary set of examples that was designed to teach key capabilities for good software engineering with inheritance. You learned how to create a derived class using inheritance, how to use protected base-class members to enable a derived class to access inherited base-class data members and how to redefine base-class functions to provide versions that are more appropriate for derived-class objects. In addition, you learned how to apply software engineering techniques from Chapter 9 and this chapter to create classes that are easy to maintain, modify and debug.

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

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