7Inheritance and Derivation

Programming mainly aims at describing and solving problems in the real world. Class in C++ effectively adopts human thinking methods of abstraction and classification. The relationship between an object and a class appropriately reflects the relationship between an individual and a group that has common features. A closer observation of the real world reveals that different things are not independent from each other, but instead they have complicated relationships. Inheritance is one such relationship. For example, children often resemble their parents, while clear distinctions exist between them. Both cars and bicycles belong to an abstract category: vehicles, but they differ from each other not only in appearance but also in performance.

Object-oriented programming provides the inheritance mechanism of class. It allows programmers to define more specific and detailed classes based on the characteristics of an existing class. We say that class A inherits from class B if A is a new class created on the basis of B. The advantages of inheritance are that codes can be reused and extended. The inheritance mechanism allows for the full utilization of existing researches and achievements. Also, after a software product has been developed, if new problems arise or we have better understandings of the original problem, the inheritance mechanism allows for efficient modifications or extensions of the existing software.

The process of deriving a new class from an existing class often includes three steps: accepting the existing class members, adjusting the existing class members, and adding new class members. This chapter is focused on this process of derivation. We will discuss the access controls of base class members, and the additions of constructor and destructor under different inheritance modes. We will then discuss the unique identification and visibility issues of class members under more complicated inheritances. In the last section of the chapter, two practical examples of inheritance will be given, respectively demonstrating how to solve a set of linear equations by the Gaussian pivoting elimination method and how to construct a personnel information management system for a small corporation.

7.1Inheritance and Derivation of Class

7.1.1Instances of Inheritance and Derivation

The inheritance and derivation hierarchy of classes is a reflection in program designs of the procedure that people use to classify, analyze, and understand problems. Things in the real world are mutually related and interacting. In the process of understanding problems, we classify them according to their common characteristics and differences for more efficient analysis and description. Figure 7.1 is an example of the classification of vehicles. The classification tree reflects the derivation relations of different vehicles. The top layer of the tree has the highest degree of abstraction, representing the most universal and common concept. Each lower layer has all the features of its upper layer, while adding its own features. The lowest layer represents the most concrete concepts. In this hierarchy, from top to bottom it is a specification process, and from bottom to top it is a generalization process. The relationship between upper and lower layers can be seen as the relationship between base classes and derived classes.

Fig. 7.1: UML hierarchy of vehicles.

Recall the examples of the personnel information management system in Chapters 4 to 6. The problem concerns a small corporation that has four groups of people: managers, part-time technicians, sales managers, and part-time salesmen. Our task is to store the name, ID number, rank, and the monthly salary of each staff, to calculate the total salary and to display all the information. Among these items, a staff ID should be generated at the time the staff information is created, which is the current maximum staff ID plus 1. The current maximum staff ID will be automatically increased by 1 each time we create a set of information for a staff member. These functionalities were realized in Chapter 6. In this chapter, we will implement specific promotion functions according to different groups of people. The calculation methods of the salaries are: a manager gets a fixed salary; a part-time technician gets a salary at an hourly rate; a part-time salesman gets a commission based on his sales; and a sales manager gets both a fixed salary and a commission.

We cannot describe the information of the four groups of people with one class since they have different processing methods (e.g., different ways of calculating their salaries). In this case, we can describe the four groups of people by four classes respectively. On the other hands, the four classes have many attributes in common, e.g., name, ID no., rank, and salary. There are also some member functions that each class has but are implemented in different ways, e.g., promotion function, salary calculating function, etc.

According to common sense, the simplest way to describe these four groups of people is to first describe the common characteristics of the entire staff, including the functions each staff member should have (e.g., calculating salary, although their calculating methods are different). When describing a specific group of the staff, we should firstly illustrate that they are staff members of this corporation, i.e., they share the common characteristics with all staff members; then we should describe the specific characteristics of this group (e.g., we should specify the commission rate for the salesmen and the hourly wage for part-time technicians), and provide the specific implementations in this group for functions of common uses (e.g., different methods for calculating salary for different people). Reflected in the object-oriented program design, this description method is the inheritance and derivation of classes. The uniform description of all the staff members forms a base class (or parent class), while the specific description of each group of people is implemented by a new class (called the child class) derived from the base class.

Inheritance preserves the attributes and behaviors of ancestors. The inheritance of a class means that the new class retains all the characteristics of the existing class. In other words, generating a new class from an existing class is the derivation of a class. The inheritance and derivation mechanism of classes enables programmers to make more specific modifications and extensions while keeping the characteristics of the original class. The new class includes the characteristics of the original class and its own new characteristics. The original class is called the base class or parent class. The newly generated class is called the derived class or child class. The derived class can also serve as a base class to generate new classes. This process forms a hierarchy of classes. The derivation of classes is a process of evolvement and development, i.e., to construct a new class from an existing class by extension, modification, and specialization. We can construct a family of objects that have common features through class derivation; hence the code can be reused. This inheritance and derivation mechanism greatly facilitates the development and modification of existing programs.

7.1.2Definition of Derived Class

In C++, the syntax form of defining a derived class is

Fig. 7.2: UML of multiple inheritance and single inheritance. a: multiple inheritance, b: single inheritance.

For example, if base classes Base 1 and Base 2 have already been defined, the following statements define a derived class Dr1, which is derived from Base 1 and Base 2.

The base class names in the definition statement (e.g., Base 1 and Base 2) are the names of existing classes, while the derived class name (e.g., Dr1) is the name of the newly generated class that inherits the characteristics of the existing classes. The case of a derived class having more than one base class is called multiple inheritance. In this case, the derived class has inherited characteristics from more than one existing class. The example illustrated above is an example of multiple inheritance. If a derived class has only one direct base class, then this case is called single inheritance. Single inheritance can be seen as the easiest case of multiple inheritance, and multiple inheritance can be seen as a combination of several cases of single inheritance. Multiple inheritance and single inheritance have many features in common. We will study the simpler single inheritance first.

In the process of derivation, the derived class can also serve as a base class to derive new classes; moreover, we can also derive many classes from one base class. That is to say, the characteristics of a derived class A, which is inherited from A’s parent class, can in turn be inherited by a new class that is derived from class A; moreover, the characteristics of a parent class can be inherited by many child classes. In this way, a family of related classes is formed, whichwe sometimes call a ‘class family.’ In a class family, classes that derive a child class directly are named the direct base classes of the child class; the upper-layer base classes of the direct base class are named the indirect base classes of the derived class. For example, the class Automobile is derived from the class Vehicle and the class Truck is derived from the class Automobile, so the class Automobile is the direct base class of the class Truck, and the Vehicle class is the indirect base class of the Truck class.

In the definition of a derived class, besides specifying its base classes, the inheritance modes for each base class should also be specified. Inheritance mode specifies how to access members inherited from a base class. In the definition of a derived class, each inheritance mode only qualifies the base class that closely follows it. The key words of inheritance mode include: public, protected, and private, representing public inheritance, protected inheritance, and private inheritance respectively. If no inheritance mode is specified, the default mode is private. The inheritance mode indicates the access permissions of the members inherited from the base class through the members or objects of the derived class. We will discuss more details in the following section.

In the previous example, class Base 1 is inherited publicly and class Base 2 is inherited privately. Besides, the derived class declares its own constructor and destructor.

Derived class members refer to the newly added data and function members in the derived class that are not inherited from the base class. These new data and function members are the key elements by which the derived class differs from its base class, and they are the evidence of the development from the base class to the derived class. When we reuse and extend existing codes, we add new members in the derived class to gain new attributes and functions. This is the evolution and development of classes based on inheritance.

7.1.3The Generation Process of Derived Classes

In C++ programming, a derived class is generated after the class has been declared and the member functions have been implemented. Once we have created the objects of a derived class, we can then use them to deal with actual problems. The process of generating a new derived class involves three steps: accepting base class members, modifying base class members, and adding new members. The main purpose of the inheritance and derivation mechanism is to reuse and extend code. Here, accepting base class members is code reusing, while modifying base class members and adding new members are code extending, which supplement each other. We will use the personnel information management system introduced in Section 7.1.1 as an example to illustrate these steps. The base class employee and the derived class technician are defined below. The function implementations of the classes are omitted temporarily, and the complete program of this example will be presented later in Section 7.7.

1.Accepting Base Class Members

The first step of class inheritance in C++ is to completely accept base class members. The derived class includes all the base class members except for the constructor and the destructor. The constructor and the destructor are not inherited in the process of derivation, which will be discussed in Section 7.3. Thus, the derived class technician inherits all the members from the base class employee except for the constructor and the destructor, including: name, individualEmpNo, grade, accumPay, employeeNo,pay(), promote(int), SetName(char *), GetName(), GetindividualEmpNo(), Getgrade(), and GetaccumPay(). These members will be reserved in the derived class after the inheritance.

2.Modifying Base Class Members

There are two aspects to modifying base class members: one is the change of access attributes of base class members, which mainly depends on the inheritance mode specified in the definition of the derived class, which will be discussed in Section 7.2; the other is the overriding of data or function members inherited from the base class. That is, declaring a data or function member in the derived class with the same name as a member inherited from the base class. If the derived class declares a new member A with the same name as another member B inherited from its base class (if A and B are functions, then their argument lists should be the same; otherwise they should form a function overloading instead of the overriding here), then the new member A will cover the member B, and we can access only A instead of B using the members or objects of the derived class. This case is called homonymy cover. Take the function pay() for example. The function pay() in the derived class technician covers the homonymous function inherited from the base class employee.

3.Adding New Members

The core of the inheritance and derivation mechanism is adding new members in the derived class, which ensures the development of the derived class. We can add appropriate data and function members in the derived class based on actual needs. In the previous example, new data members hourlyRate and workHours are added to the derived class technician.

Because constructors and destructors cannot be inherited from the base class in the class inheritance, to realize some special initialization and cleaning up work, new constructors and destructors should be added in the derived class. For example, we design the constructor technician() in the derived class technician.

This chapter is organized according to the three steps of derivation procedure described above. The first step in fact is finished once a derived class is defined and needs no intervention of programmers, hence we will not further discuss it. As for the second step, wemainly study the issues of access controls of the base class members under different inheritance modes. For the third step we focus on the addition of the constructor and destructor, because the methods and rules of adding other ordinary members in a derived class are the same as those when defining an ordinary class, to which readers can refer in Chapter 4. We will then discuss the issues of unique identification and access of class members under more complicated inheritance relationships. Finally, we will give instances of class inheritance as the review and summary of this chapter.

7.2Access Control

The derived class inherits all the data members and function members from the base class except for the constructor and the destructor. However, the access attributes of these inherited members can be changed in the process of inheritance. The access attributes of inherited members are indicated by the inheritance mode specified in the definition of the derived class.

Base class members have three kinds of access attributes: public, protected, and private. Members of a base class can access any other members of the same class, while through objects of a base class we can only access the public members of that class.

There are three inheritance modes: public inheritance, protected inheritance, and private inheritance. Different inheritance modes result in different access attributes of the base class members in the derived class. There are two kinds of access situations: the first is new members in the derived class accessing members inherited from the base class; the second is accessing members inherited from the base class through objects of the derived class outside the derived class. We will discuss these different cases respectively.

7.2.1Public Inheritance

If the inheritance mode is public, then in the derived class, the access attributes of the public and protected members inherited from the base class will not change, but the private members inherited from the base class cannot be directly accessed. That is to say, in public inheritance, the access attributes of public and protected members inherited from the base class will not change– they will remain public and protected members in the derived class and other members in the derived class can access them directly. Outside the class family, we can access public members inherited from the base class only through objects of the derived class. Neither the derived class members nor objects of the derived class can access private members of the base class directly.

Example 7.1: Public inheritance of the class Point.

The class Point has been mentioned many times in previous chapters. In this example, we will derive a new class Rectangle from the class Point. A rectangle is specified by a point, a width, and a length. The point of a rectangle has all the characteristics of the class Point, but a rectangle has its own features. This means that we should add new members while inheriting the class Point. The inheritance relations can be described in UML, as shown in Figure 7.3.

Let us start with the header file.

Fig. 7.3: UML of the inheritance relations between class Point and class Rectangle.

Here, the base class Point is declared first. The derived class Rectangle inherits all the members that class Point has except the constructor and the destructor. So the members that the derived class has are the sum of the members inherited from the base class and the new members. The inheritance mode is public, thus public members inherited from the base class retain their access attributes in the derived class and are accessible by the member functions and objects of the derived class (e.g., the member function InitR in the derived class can call the member function InitPinherited from the base class directly). Private members of the base class (e.g., X and Y in the base class), on the other hand, are not accessible by the member functions and objects of the derived class. The original external interfaces of the base class (e.g., function GetX() and GetY() in the base class) now become a part of the external interfaces of the derived class. Obviously, the new members of the derived class can access each other.

The class Rectangle inherits all the members of class Point, thus realizing the reuse of code. Besides, by adding new members, the class Rectangle owns its new characteristics, and thus the code is extended.

The main() of the program is as follows:

An object rect of the derived class is created first in main(). The default constructor, which does nothing, is generated automatically by the system when the object is created. Then we call the public functions InitR and Move of the derived class and the public functions GetX() and GetY() inherited from the base class through the object of the derived class. In this example, we see under public inheritance, how to access public members inherited from the base class through member functions and objects of the derived class.

The running result of the program is:

7.2.2Private Inheritance

If the inheritance mode is private, then after the inheritance, the public and protected members inherited from the base class will become private members of the derived class, and the private members inherited from the base class cannot be accessed directly in the derived class. That is to say, all the public and protected members of the base class will become private members after inheritance– they can be accessed directly by other derived class members, but cannot be accessed directly by objects of the derived class outside the class family. Neither derived class members nor objects of the derived class can access private members inherited from the base class.

Since after private inheritance, all the base class members become private or inaccessible members of the derived class, further derivations will result such that no member of the original base class will be accessible directly in newly derived classes. Therefore, after private inheritance, base class members will no longer play a role directly in further derived classes. In fact, private inheritance terminates further derivations of the original base class. Thus, private inheritance is less often used.

Example 7.2: Private inheritance of class Point.

The problem this example handles is the same as the one in Example 7.1, but this time we use a different inheritance mode during the inheritance. The following is the class definition in the program:

Similarly, the derived class Rectangle inherits the members of class Point. Therefore, the members of the derived class consist of the members inherited from the base class and the newly generated members in the derived class. The inheritance mode is private, which means that all the public and protected members in the base class become private members in the derived class and that private members of the base class (e.g., X and Y in the base class) cannot be accessed by the member functions and objects of the derived class. Members of the derived class can still access public and protected members inherited from the base class (for instance, we can call function InitP inherited from the base class in the member function InitR of the derived class), but members inherited from the base class cannot be accessed through objects of the derived class outside the derived class. The original external interfaces of the base class (for example, GetX() and GetY() in the base class) are encapsulated and hidden by the derived class. Of course, new members of the derived class can access each other freely.

Under private inheritance, in order to ensure that some external interfaces of the base class remain public in the derived class, we should declare members with the same name of the original external interfaces in the derived class. In the derived class Rectangle, we declare functions Move, GetX, and GetY again, using the access ability of the derived class to the members of the base class to copy their functionality into the derived class. The scope of the member functions redeclared in the derived class is smaller than that of the functions with the same name declared in the base class. So the function declared in the derived class will be called automatically when a function call with that name is made, according to the overlapping rule for functions with the same name. In program designs, this overlapping rule is a key method and is often used to modify original functions.

The main() in the following is identical to that in Example 7.1, but their executions are different.

The main difference between this main() function and that in Example 7.1 is that here all the functions that the object rect of the class Rectangle calls are public members declared by the derived class itself. Because this is a private inheritance, we cannot access any member inherited from the base class through the object rect. Compared with the public inheritance in Example 7.1, here we only modify the definition of the derived class, and the base class and the main() remain without change. Readers can see the advantage of encapsulation in object-oriented programming: while the external interfaces of the class Rectangle remain the same, modifications of the implementation of internal members will not influence other parts of the program. This is an example of code reusability and extendibility in object-oriented programming. The running result of this program is the same as that in the previous example.

7.2.3Protected Inheritance

Under protected inheritance, public and protected members inherited from the base class will become protected members in the derived class, and the private members inherited from the base class are not accessible directly in the derived class. In this way, members in the derived class can access the public and protected members inherited from the base class directly, while they cannot be accessed through objects of the derived class outside the class family, as shown in Figure 7.4. Neither members nor objects of the derived class can directly access the private members inherited from the base class.

Fig. 7.4: UML of the access rules for protected class members.

Comparing private inheritance with protected inheritance, we can see that all the access attributes of members in the directly derived class after inheritance are the same in the two cases. However, the difference appears when we use the derived class as a new base class to further derive classes. Suppose class Rectangle inherits class Point privately and class Square in turn inherits class Rectangle: then neither the members nor the objects of class Square can access members inherited indirectly from class Point. If class Rectangle inherits class Point in a protected way, then all the public and protected members in class Point will become protected members in class Rectangle. Public and protected members in class Point will become protected or private (depending on the inheritance mode between class Rectangle and class Square) in class Square. Therefore, members of class Square may access the members indirectly inherited from class Point.

From the access rules of inheritance, we can see the characteristics of protected members in a class. If class Point has protected members, then like private members, these protected members are not accessible through objects of class Point. If we derive a child class Rectangle from class Point, then for class Rectangle, protected members and public members have the same access attributes. In other words, protected members inherited from class Point can be accessed directly by the members of the derived class Rectangle, but can never be accessed by other external users (e.g., common functions in the program or other classes parallel to class Point). By using protected members properly, we can find a balance between data sharing and data hiding in complex layer relationships among classes, thus realizing the efficient reuse and extension of codes.

Example 7.3: Access protected members.

We will detail the above discussions through two simple examples. Suppose an arbitrary class A has a protected data member x; we will discuss the access attribute of x.

The definition of class A is:

If main() is:

An error will be detected in the compile time. The error is that the module establishing the object of class A, i.e., main(), tries to access the protected member of class A, which is not allowed because the access attribute of the protected member of class A is the same as that of the private members of class A. This illustrates that the protected members of class A are not accessible in the module establishing object a of class A. In this case, protected and private members are well hidden.

If we derive class B from class A publicly, then the protected and public members inherited from class A are accessible in class B. For example:

Protected members inherited from the base class are accessible using the member function function of the derived class B.

7.3Type Compatible Rule

The type compatible rule refers to how an object of a publicly derived class can substitute an object of the base class wherever the latter one is needed. The derived class obtains all the members inherited from the base class except the constructor and the destructor through public inheritance. So, the publicly derived class can do whatever the base class can. The substitution referred in the type compatible rule includes the following situations:

An object of a derived class can be assigned to an object of the base class.

An object of a derived class can be used to initialize a reference of the base class.

The address of an object of a derived class can be used to assign to a pointer of the base class.

After substitution, the object of the derived class can be used as an object of the base class. However, here only the members inherited from the base class can be used.

If class B is a base class and class D is a publicly derived class from class B, then class D owns all the members of class B except the constructor and the destructor. According to the type compatible rule, an object of the derived class D can substitute an object of the base class B wherever the latter one appears in the program. In the following program, b1 is an object of class B and d1 is an object of class D.

Here:

1.An object of the derived class can be assigned to an object of the base class, i.e., to assign the value of each member in the object of the derived class, which is inherited from the base class, to the corresponding member of an object of the base class:

2.An object of the derived class can also be used to initialize the reference of a base class object.

3.The address of an object of the derived class can be assigned to a pointer of the base class.

Thanks to the type compatible rule, we can use the same functions to process objects of the base class and objects of the publicly derived class (because when a formal argument of a function is an object of the base class, the actual argument can be an object of the derived class). Since there is no need to design specific modules for every class, the program efficiency is greatly improved. This is another important feature of C++, i.e., polymorphism, which will be discussed in the next chapter. We can say that the type compatible rule is one of the important foundations of the polymorphism. In the following example,weusethesame function to deal with objects in the same class family.

Example 7.4: Example of type compatible rule.

Class B1 is publicly derived from the base class B0, and class D1 is in turn publicly derived from class B1. The member function display() in base class B0 is overridden in the derived classes. The derivation relations among these classes are as shown in Figure 7.5.

The program is:

Fig. 7.5: UML of the derivation relations among the classes.

In the above example, by using the terms “object name. member name” or “object pointer−>member name”, we can access newly added homonymic members in the derived classes. According to the type compatible rule, we can assign the address of an object of the derived class to a pointer of base class B0, while only the members inherited from the base class can be accessed through this pointer.

In the program, we declare an ordinary function fun with a pointer of base class B0 as its formal argument. Based on the type compatible rule that the address of an object of the publicly derived class can be assigned to a pointer of the base class, function fun can operate uniformly on the objects of this class family. In the execution of the program, the addresses of an object of the base class, an object of the derived class B1, and an object of the derived class D1, are respectively assigned to the base class pointer p. However, through pointer p we can only access members inherited from the base class. That is to say, although the pointer points to an object of the derived class D1, during the execution of the function fun we can only access the member function display inherited from the base class B0 instead of the homonymic function declared in class D1 through the pointer p. Thus, the three calls of function fun in main() have the same results: they all access the public member function declared in the base class. The execution results are as follows.

From this example, we can see that an object of the derived class can substitute an object of the base class according to the type compatible rule, but the object of the derived class only plays the role of the base class after the substitution. In the next chapter, we will learn another key feature of object-oriented programming: polymorphism. Through polymorphism, the base class and the derived class can give different responses to the same message, on the premise of the type compatible rule.

7.4Constructor and Destructor of Derived Class

The purpose of inheritance is to develop. The derived class inherits the members of the base class, realizing code reuse. But code expansion is more important. Only by adding new members and functionalities can we make the derivation meaningful. In previous examples, we have studied the addition of ordinary members in derived class. In this section, we will focus on the constructor and the destructor of derived class. Since the constructor and the destructor of the base class cannot be inherited in the derivation, if we want to initialize the newly added members in the derived class, we have to add new constructor in the derived class. However, the constructor of the derived class can only initiate members newly added in the derived class, while the members inherited from the base class will still be initiated by the constructor of the base class. New destructors should also be added to the derived class to do the cleanup work of objects in the derived class.

7.4.1Constructor

After defining a derived class, we need to create an object in the derived class in order to use the derived class. Objects must be initialized before being used. The data members of the derived class consist of the data members inherited from the base class and newly added data members in the derived class. If the new data members of the derived class include inline objects of other classes, the data members of these object members are also indirectly included in the data members of the derived class. Therefore, data members of the base class, new data members, and data members of object member should all be initialized when creating an object of the derived class. Since the constructor of the base class is not inherited, in order to implement the initialization of objects of the derived class, we must add a new constructor to the derived class. The constructor of the derived class needs appropriate initial values as arguments. Some of the arguments are used to initialize the new members in the derived class, while others are used to pass on to the constructors of the base class and the inline object members so as to initialize the corresponding members. When creating an object of the derived class, the constructors of the base class and the inline objectmembers are first called implicitly to initialize the corresponding data members; then the function body of the derived class constructor is executed.

The syntax form of the definition of a derived class constructor is as follows:

Here, the name of the constructor is the same as the name of the derived class. The argument list of the constructor should provide all the arguments needed to initialize the base class data, new inline object data, and newly-added member data. Following the argument list, the names of the base class and the inline members that need arguments for initializations, along with their associated argument lists, should be listed. Each item is separated by commas. The order of the names of base classes and of objects does not matter: they can appear in any order. When generating an object of the derived class, the system will first use the arguments listed here to call the constructors of the base classes and of the inline objectmembers.

If a derived class has more than one base class, then for all the base classes and all the inline objectmembers that need arguments for initializations, their names and argument lists should be given explicitly. For those base classes that use default constructors, their names and argument lists need not be given. As for single inheritance, we need to give the name and argument list of only one base class.

Now, let us discuss when we should declare the construction of the derived class. If the base class declares a constructor with formal arguments, then the derived class should declare its constructor. This provides a way to pass arguments to the constructor of the base class, ensuring that the base class can get necessary data during its initialization. If the base class does not declare a constructor, the derived class can choose not to declare its constructor. In such a case, the default constructor will be used and the task of initializing new members can be done by other public functions.

The common execution order of calling constructor of a derived class is as follows:

  1. Call the constructors of the base classes. The calling order is the same as the order of these base classes appearing in the definition of the derived class (from left to right).
  2. Call the constructors of the inline object members. The calling order is the same as the declaration order of these object members in the body of the derived class.
  3. Execute the constructors of the derived class.

Only when the new members of the derived class contain inline objects will the second step be executed. Otherwise, the second step is skipped and the constructor of the derived class is executed immediately after the first step. The calling order of the constructors of the base classes is the order of these base classes appearing in the definition of the derived class, and the calling order of the constructors of inline object members is the declaration order of these object members in the body of the derived class. Note that the execution order of these constructors has nothing to do with the order listed in the constructor of the derived class.

Example 7.5: Example of constructor of derived class (multiple inheritance, has inline object members).

This is an example with general features. There are three base classes B1, B2, and B3. The only member of class B3 is a default constructor. The only member of both class B1 and class B2 is a constructor with one argument. Class C is derived publicly from the three base classes. The derived class has three private object members, which are objects of class B1, B2, and B3 respectively. The program is as follows:

Let us analyze the characteristics of the constructor of the derived class. Because the base classes and the inline objectmembers all have nondefault constructors, a nondefault constructor (i.e., a constructor with arguments) is required in the derived class. The main task of the constructor of the derived class is to initialize the base classes and the inline object members, by the rules we mentioned before. The definition of the constructor of the derived class constructor is:

The argument list of the constructor provides all the arguments needed for the initialization of the base classes and the inline objectmembers. Following the colon are the names of the base classes and the inline objects together with their arguments lists. Here, we should notice two things. Firstly, we do not list all the base classes and the member objects. Because class B3 has a default constructor and there is no need to pass arguments to it, we need not list class B3 and the object memberB3 here. Secondly, the order of the base classes and the object members in the argument list is arbitrary. The body of the constructor of this derived class constructor is empty, so it is only used to call the constructors of the base classes and the inline object members and pass on arguments to them.

The main() declares an object c of the derived class C. The constructor of the derived class is called when c is created. Let us consider the execution of the constructor of class C. Firstly the constructors of the base classes should be called; then the constructors of the inline object members should be called. The calling order of the constructors of the base classes is the order of these base classes appearing in the definition of the derived class, i.e., B2, B1, and then B3. The calling order of the constructors of the inline objectmembers is the declaration order of these object members in the body of the derived class, i.e., B1, B2, and then B3. The execution result proves the above analysis, which is as below:

In the declaration of the constructor of the derived class, the names of class B3 and the object memberB3 of class B3 are not listed explicitly, so the system will call the default constructor of class B3 automatically. If a base class declares both the default constructor and constructor with arguments, then in the declaration of the constructor of the derived class, we can either list the name and corresponding arguments of the base class or not, depending on the actual needs.

7.4.2Copy Constructor

How does write copy constructor when there is an inheritance? For any class, if the programmer does not write a copy constructor, then the compiler will generate a default copy constructor when necessary. If the default copy constructor is called when creating an object of a derived class, then the compiler will automatically call the copy constructor of the base class.

If we want to write a copy constructor for the derived class ourselves, we need to pass arguments to the copy constructor of the base class. For example, suppose class C is a derived class from class B. The copy constructor of class C is as follows:

Readers might get confused by the fact that the argument type of class B’s copy constructor should be the reference of class B‘s objects, but here the reference c1 of class C’s objects is used as the argument of class B’s copy constructor. This is because of the type compatible rule: we can use the reference of the derived class to initialize the reference of the base class. So when the formal argument is a reference of the base class, the actual argument can be a reference of the derived class.

7.4.3Destructor

The destructor of a derived class is used to do necessary cleanup work before an object of the derived class disappears. Destructor has neither type nor arguments. So it is simpler than the constructor.

The destructor of the base class cannot be inherited in the derivation. A new destructor should be declared in the derived class if needed. The method of declaring a destructor in the derived class is the same as that in an ordinary class without any inheritance relationships. The only responsibility of the destructor of a derived class is to clean up the new nonobject members of the derived class in its body. The system will call the destructors of the base classes and the object members automatically. The execution order of destructors is just the opposite of that of the constructors. Firstly the new ordinary members of the derived class should be cleaned up, and then the new object members, and finally the members inherited from the base class. This cleanup work is accomplished by executing the destructors of the derived class, the object members, and the base classes respectively.

In previous examples in this chapter, we have not declared destructors of any class explicitly. In such cases, the compiler will generate a default destructor for each class, which will be called at the end of the object’s life period. Such a destructor does nothing but formally complete the destruction process.

Example 7.6: An example of destructor of derived class (multiple inheritances, has inline object members).

We modified Example 7.5 by adding destructors to all the base classes. The program is shown below:

In this program, we add destructors to all the three base classes, but make no changes to the derived class. The derived class will still use the default destructor provided by the system. The main() also remains the same. During the execution, the constructor of the derived class will be executed first, then the destructor of the derived class. We discussed constructors in the previous section. The default destructor of the derived class in turn calls the destructors of the member objects and the base classes. The calling order is just the opposite of that of the constructors. The execution result is as follows:

The output proves our analysis. Here we only illustrate the syntax rules of constructors and destructors of derived class using a simple example. In latter examples we will continue to discuss them, combining their uses.

7.5Identification and Access of Derived-Class Member

We have learned the syntax rules of class inheritance and derivation around three procedures: accepting base class members, modifying base class members, and adding new members. A class family is formed through class derivation. In this section, we will focus on some issues in the uses of derived classes, including the identification and access of the derived class and its object members (including members inherited from the base class and newly added members).

In the derived class, members can be classified into four groups according to their access attributes:

Inaccessible Members

These members are inherited from the private members of the base class. Neither the derived class nor the modules creating objects of the derived class can access them. If the derived class continues to derive new class, these members remain inaccessible.

Private Members

These members can be inherited from the base class or newly added members. They are accessible inside the derived class, but are inaccessible in the modules establishing objects of the derived class. Further derivation will make these private members become inaccessible members in newly derived classes.

Protected Members

These members can be inherited from the base class or newly added members. They are accessible inside the derived class, but are inaccessible in the modules establishing objects of the derived class. Further derivation will make these protected members become private or protected members in newly derived classes.

Public Members

These members can be accessed by the derived class or the modules establishing objects of the derived class. Further derivation will make these public members become private, protected, or public members in newly derived classes.

Two problems remain to be solved in accessing a derived class. The first one is about unique identifiers and the other is about the access attributes of class members, or more specifically, their visibility. We can only access visible members with their unique identifiers. If more than one member can be referred to by one expression, then this case is called ambiguity. The ambiguity problem is the unique identifier problem that will be discussed in this section.

7.5.1Scope Resolution

The scope qualifier is represented by the symbol “::”. It is used to specify the class to which the member we want to access belongs. The general syntax form of its use is:

Next, wewill see how the scope qualifier uniquely identifies members in the class family’s hierarchy.

For identifiers declared in different scopes, the visibility rule is as follows: For two or more scopes that have inclusion relationships, if an identifier is declared in the outer layer and there is no homonymic identifier declared in the inner layer, then the outer layer’s identifier is visible in the inner layer; if a homonymic identifier is declared in the inner layer, then the outer layer’s identifier is invisible in the inner layer: at this time, the inner layer’s variable hides the outer layer’s homonymic variable, and this is called the name hiding rule.

In the hierarchy of class derivation, both the base class members and the new members in the derived class have class scopes, while the two scopes are different and have inclusion relationships. The derived class is in the inner scope. If the derived class declares a new member whose name is the same as a member in the base class, then the new member of the derived class will hide the homonymic member inherited from the base class. Using the name directly can only access the newly declared member of the derived class. If a new function, whose name is the same as a function in the base class, is declared in the derived class, even if it has a different argument list from those declared in the base class, all the overloading forms of the homonymic functions inherited from the base class will be hidden. Toaccess the hidden members, we should use the scope qualifier to qualify the name of the homonymic function of the base class.

For multiple inheritance, let us first consider a simple case where there are no inheritance relationships among the base classes and there is no common base class. The most typical case is that no base class has a superior base class. Suppose two or more base classes of a derived class have members with the same name; if the derived class has a new homonymic member, then the derived class’s member will hide all members with the same name in the base classes. The new member in the derived class can be uniquely identified and accessed through the form “object name. member name”. The members in the base class can be accessed by the name of the base class and the scope qualifier; if there is no new member declared in the derived class that has the same name as the homonymic members of the based classes, then “object name. member name” cannot uniquely identify a member. At this time, all the members inherited from the different base classes have the same name and same scope, and thus the system cannot judge which class member should be called. This problem can also be solved by using the name of the base class together with the scope qualifier. See the following example.

Example 7.7: Example of name hiding in multiple inheritance.

In the following program, two base classes, B1 and B2, are defined. A new class D1 is derived publicly from base classes B1 and B2. The two base classes both declare data member nV and function fun. Two new members are added to the derived class, one named nV and the other named fun. So class D1 has six members, and each group of three has the same name. Figure 7.6 shows the derivation relationships among the classes and the structure of the derived class.

Fig. 7.6: The derivation relations of derived class D1 in multiple inheritance and D1’s member structure.

Now we will analyze the access issues of the derived class. The new members in the derived class have a smaller class scope, so in the derived class and the module creating the derived-class objects, the new members in the derived class hide the homonymic members derived from the base class. We can access new members of the derived class by using the term “object name . member name”. The members derived from the base class can be accessed by using the name of the base class and the scope qualifier, telling the system which member of which class is to be called. The source program is as below:

An object d1 of the derived class D1 is created in main(). According to the hiding rule, if we call a homonymic member in the derived class only through the member name, then only the new member declared in the derived class will be accessed, and the members inherited from the base classes will be hidden because they are in the outer layer. To access the homonymic member inherited from a base class, we must use the name of the base class and the scope qualifier. The statements at the end of the program aim at accessing the members inherited from base class B1 and B2 respectively. The result of execution is:

By using the scope qualifier, we can specify the base class from which the member in the derived class is inherited, and thus solve the problem of member hiding.

In this example, if the derived class does not declare a member with the same name as any member inherited from the base classes, then no member can be accessed by using the term “object name. member name”. The homonymic members inherited from class B1 and from B2 have the same scope, so the system cannot identify uniquely which member is to be called. In such a case, it is necessary to use the scope qualifier.

If no new member is added to the derived class in the program of Figure 7.6, then the definition of the derived class is modified as follows.

In this case, if the rest of the program remains unchanged, then using “object name . member name” to access class members in main() will result in errors.

We assume there is no inheritance relationship among the base classes in the above example. What if this assumption is not satisfied? If some or all of a derived class’s direct base classes are derived from another common base class, then in these direct base classes, all the members inherited from the upper base class will have the same names. In this case we must also use a scope qualifier to identify unique members, which also have to be defined by the direct base class.

Consider the following example. A data member nV and a function fun are declared in the base class B0. Class B1 and B2 both are derived publicly from class B0, and class D1 is derived publicly from both class B1 and B2. There is no new member with the same name as any member inherited from the base classes in the derived class (if there is, it should obey the hiding rule). In this case, class D1 has members nV and fun inherited from both class B1 and B2. The derivation relations among the classes and the structure and memory allocation of the derived class are shown in Figure 7.7.

Now let us focus on the issues of identification and the access of member nV and fun. Each of the two members of the indirect base class B0 appears as two homonymic members in derived class D1 through different paths after two derivations. At this time, even if we use the indirect base class name B0 to qualify an inherited member, it is still unclear whether the member is inherited from class B1 or from B2. In this case, we must use the direct base class name to qualify the inherited member, as shown in the following source code.

Fig. 7.7: The derivation relations among the classes and the structure and memory allocation of the derived class.

In main(), an object d1 of the derived class D1 is created. The system cannot uniquely identify which member is to be accessed only through the object name. In this case, the scope qualifier should be used. Using the name of the direct base class to qualify a member can determine which member inherited from the base class is to be accessed. The result of execution is:

In this case, there are two copies of member nV and of member fun with the same names in the memory of a derived-class object. The two nV can be initialized by the constructor of B0 called by B1 and B2 respectively, and thus the two nV can store different values. They can also be separately accessed by using the direct base class name to qualify the name nV. However, in most cases we only need one copy of the base-class members. Multiple copies will only result in more memory cost. This problem can be solved by the virtual class mechanism provided by C++.

7.5.2Virtual Base Class

If some or all of a derived class’s direct base classes are derived from the same base class, then the members of the direct base classes inherited from the upper base class share the same names. Then the data members declared in the indirect base class will have multiple copies in memory for a derived class object and each function declared in the indirect base class will have multiple mappings. We can utilize the scope qualifier to access them separately, or we can also set the common base class as a virtual base class. In the latter case, there will be only one copy in memory for data members with the same name inherited from different paths and only one map for each function name. This solves the unique identification problem for the members with the same name.

Virtual base class is declared during the definition of the derived class. Its syntax form is:

Fig. 7.8: The derivation relations among the classes, and the structure and memory allocation of the derived class.

The above statement declares that the base class of the derived class is a virtual base class. In multiple inheritance, as the specified inheritance mode, the key word “virtual” takes effect only on the base class that follows it closely. After declaring the virtual base class, the members of the virtual base class will maintain the same data copy in memory as the derived class in further derivations.

Example 7.8: Example of virtual base class.

Let us consider the problem mentioned before. Data member nV and function fun are declared in the base class B0. Class B1 and class B2 both are derived publicly from B0. The difference from the previous example is that B0 is declared as a virtual base class during the derivations. A new class D1 is derived from B1 and B2 publicly. We do not add new members with the same name in class D1. (If there is a new member with the same name, it should obey the hiding rule.) So in class D1, members nV and fun in base class B0 will have only one copy respectively. The derivation relations among the classes and the structure and memory allocation of the derived class are shown in Figure 7.8.

There is only one copy of data member nV and function fun in the derived class D1 after using the virtual base class mechanism. In the module, when creating objects of class D1, the members are accessible by using the term “object name .member name”.

Note: the declaration of a virtual base class just uses the key word virtual during the class derivation. In main(), a derived-class object d1 is created and d1’s members nV and fun can be accessed by the member names. The execution result is:

Consider the personnel information management problem mentioned at the beginning of this chapter. In the system, we need to take into account four groups of people. In Section 7.1, we defined the base class employee and derived the class technician. Now let us consider the definitions of other groups. Both part-time salesmen and managers should be inherited from the base class employee. Since sales manager belongs to both the manager group and salesman group, it has the characteristics of the two groups. For example, a sales manager’s monthly salary is composed of two parts: the fixed salary and sales commissions. This clearly shows that the sales manager should be derived from both the manager group and the salesman group. The definitions of the classes are:

Here both salesman and manager are inherited from the virtual base class employee. So in the further derived class salesmanager, there is only one copy of base class employee’s members derived from different paths, to avoid redundant copies. Each class has the function pay(). The base class employee only declares the function to unify the interface of the class family. Because different derived classes have different characteristics (e.g., different ways of calculating monthly salary), the function pay() declared in class employee cannot apply to every derived class. The derived classes must declare their own function pay() to hide the function declared in the base class. You will have a better understanding of it after reading the entire program in Section 7.7.

Let us compare the two examples, which respectively use scope qualifier and virtual class mechanism. In the former case, the derived class has multiple copies of the members with the same name inherited from the base classes, whichareuniquelyidentifiedthrough the scope qualifiers of the direct base classes. These different copies can save different data values or do different operations. In the latter case, only one copy of the members inherited from the base classes is saved in the memory of the derived class. So, the former can store more data information while the latter is more precise and reduces memory cost. Programmers ought to choose appropriate approaches according to actual needs.

7.5.3Constructors of Virtual Base Class and Derived Class

In Example 7.8, the use of virtual base class is convenient and easy because all the classes use default constructors automatically generated by the compiler. If the virtual base class declares a nondefault constructor (i.e., with arguments) instead of the default constructor, things will be more complicated. In this case, all the derived classes directly and indirectly derived from the virtual base class must explicitly list the initialization of the virtual base class in the member initialization lists of their constructor’s. For example, if in Example 7.8 the virtual base class declares a constructor with arguments, then the program should be modified as follows:

Some readers may have the concern that when creating object d1 of class D1, not only the constructor of virtual base class B0 is directly called to initialize the member nV inherited from class B0, but the constructors of the direct base classes B1 and B2 are also called, which also call the constructor of base class B0 for initialization. This seems to suggest that the member nV inherited from the virtual base class is initialized three times. The C++ compiler has an elegant way to solve this problem, so we can write programs like the above example without worry. Now let us see how the C++ compiler deals with this problem. For convenience, we refer to the class to which the created object belongs as the furthest derived class. Using the above program as an example, class D1 is the furthest derived class when creating object d1. If the object has members inherited from a virtual base class, then the members of the virtual base class are initialized by the constructor of the virtual base class, which is called by the constructor of the furthest derived class. Only the constructor of the furthest derived class can call the constructor of the virtual base class. Calls on the constructor of the virtual base class from other base classes of the furthest derived class (e.g., class B1 and B2) will automatically be ignored.

7.6Program Example: Solving Linear Equations using Gaussian Elimination Method

Many problems in natural science and engineering may come down to the solving of linear equations. The Gaussian elimination method is a classical algorithm to solve linear equations. The complete pivoting elimination method, which is the improvement and deformation of Gaussian elimination method, is a common and efficient method for solving linear equations.

7.6.1Fundamental Principles

Suppose there are n linear equations, (to conveniently represent an array in C++, the suffix in equations starts from 0)

{a00x0+a01x1++a0,n1xn1=b0a10x0+a11x1++a1,n1xn1=b1an1,0x0+an1,1x1++an1,n1xn1=bn1(7.1)

The matrix form of the above equations is Ax = b, where A is the coefficient matrix, x is the column vector, which is the solution of the equations, and b is also a column vector. Generally, we can assume that A is a nonsingular matrix. For more knowledge about matrices, please refer to books on linear algebra.

a=[a00a01a0,na10a11a1,nan1,0an1,1an1,n1],x=[x0x1xn1],b=[b0b1bn1](7.2)

Put the coefficient matrix A and vector b together to make an augmented matrix B.

B=(A,b)=[a00a01a0,nb0a10a11a1,nb1ai1,j1an1,0an1,1an1,n1bn1](7.3)

The complete pivoting elimination is carried out on matrix B. The whole process can be divided into two steps:

1.Elimination

For k starting from 0 to n − 2, do the following three steps:

a)Select the element with the largest absolute value in the submatrix, which starts at row k and column k of A, as the pivot element. For example,

|ai1,j1|=max|aij|0ki<nkj<n(7.4)

Then swap the k-th row and the i1-th row, as well as the k-th column and the j1-th column. So the element with the largest absolute value in this submatrix ismoved to the k-th row and k-th column.

b)Normalization. The method is:

akj=akj/akk,j=k+1,,n1bk=bk/akk(7.5)

c)Elimination.

aij=aijaikakj,j,i=k+1,,n1bi=biaikbk,i=k+1,,n1(7.6)

2.Back Substitution

xn1=bn1/an1,n1xi=bij=i+1n1aijxj,i=n2,,1,0(7.7)

Here, we only list the steps of the complete pivoting elimination algorithm. The derivation and detailed process of the algorithm can be found in books on numeric calculation.

7.6.2Analysis of the Program Design

We can see from the above algorithm analysis that the main task in this example is the matrix calculation. We can define a class Matrix as the base class, and then derive a linear equation class Linequ from class Matrix. The class Matrix only deals with n × n square matrices and stores square matrices using a one-dimensional array. The data members of class Matrix include the starting address of the one-dimensional array and the variable n. The functions of class Matrix include setMatrix() for setting the matrix value, printM() for displaying the matrix, and so on.

According to the analysis, besides the coefficient matrix A inherited from class Matrix, the linear equation class Linequ should also include the starting address of solution vector x and vector b. The primary functions of class Linequ include setLinequ() for setting equations, printL() for displaying equations, Solve() for solving equations, and showX() for outputting the solution. We can define new members in class Linequ to implement these functions for solving equations.

The composition of classes Matrix and Linequ and their relations are shown in Figure 7.9.

The member function Solve of class Linequ needs to access the data members of base class Matrix when solving the equations. We use public derivation and set the access attributes of the data members in class Matrix as protected. Thus, after the public derivation, protected members in the base class are still protected members in the derived class and can be accessed by derived-class functions.

7.6.3Source Code and Explanation

Example 7.9: Use complete pivoting elimination method to solve linear equations.

The whole program contains three files: linequ.h, linequ.cpp, and liqumain.cpp. Classes Matrix and Linequ are defined in linequ.h. The member functions of the two classes are implemented in linequ.cpp. The main() is in leuqmain.cpp. An object of class Linequ is defined in main(), which we can use to solve the linear equations with four variables.

Fig. 7.9: The derivation relations between class Matrix and class Linequ.

Through public derivation, class Linequ inherits all the members in class Matrix except for the constructor and the destructor. Because all the base-class members are public or protected, they are accessible in the function members of the derived class. The protected members of the base class, however, are not accessible in the module creating objects of class Linequ. By using the protected access attributes and public derivation, we realize efficient sharing and reliable protection of data of the base class Matrix. In the program, we store the coefficient matrix, the solution vector, and the vector on the right side of the equations all through dynamic memory allocation, which are implemented in the constructors of the base class and the derived class. The cleanup work is done in destructors.

In the process of implementing class member functions, the constructor of the derived class calls the constructor of the base class to pass arguments and it dynamically allocates memory space for the matrix. The destructor of the derived class also calls the destructor of the base class. The entire calling procedure is completed within the system automatically. After public derivation, protected base-class members remain as protected members in the derived class, which can be accessed by the member functions of the derived class.

The return type of the function implementing the complete pivoting elimination approach is an integer. If the program terminates normally, the return value is 1; otherwise the return value is 0. According to the return value, we can determine the completion status of the solving process.

In main(), we use a quaternion equation to test our algorithm. Both the coefficients and the vector on the right side of the equations are stored in one-dimensional arrays. Firstly, an object equ1 of quaternion equations is created. During the creation process, the constructor of the derived class is called. The constructor of the derived class in turn calls the constructor of the base class, and then dynamically allocates memory spaces. Next, the coefficients and the vector on the right side of the equations are initialized, where the selected equations are input into the object equ1. The member functions printL, solve, and showX of the object complete the tasks of displaying equations, solving equations, and displaying the solution, respectively.

7.6.4Execution Result and Analysis

The result of the program is as follows.

The equations that this example uses come from the book C Programs of Common Algorithms written by Mr Xu Shiliang. The calculation result here is exactly the same as in the book. The selected equations are:

The matrix is stored in a one-dimensional array through dynamic memory allocation.

This is an example of solving practical problems through the derivation hierarchy of classes. The base class deals with the matrix. The publicly derived class Linequ is designed to deal with linear equations. In addition to the members inherited from the base class, new members are added to class Linequ according to actual needs. In this way, we detail and specialize the base class Matrix to describe and solve the problem more efficiently.

The access control is also designed according to actual needs. The matrix stored in and maintained by the data members of the base class, which is the coefficient matrix in the derived class, must be accessed by the functions of the derived class. We take advantage of the characteristics of protected members, setting the access attributes of the base-class members to be protected. Thus the protected members inherited from the base class can be accessed in the publicly derived class Linequ, while they are inaccessible by other modules outside the class. In this way, we find an appropriate balance between data sharing and data hiding.

New constructors and destructors should be added to the derived class to do the initialization and cleaning up work in the derived class because the constructor and destructor of the base class cannot be inherited. The constructor of the derived class initializes the data inherited from the base class by calling the constructor of the base class. In this example, the constructor of the derived class Linequ calls the constructor of the base class Matrix and passes on the initialization arguments. The destructor of the derived class also calls the destructor of the base class to do the cleaning up work.

7.7Program Example: Personnel Information Management Program

In this section, we will take the personnel information management of a small corporation as an example to further illustrate the procedure of derivation and the applications of virtual functions and virtual base classes.

7.7.1Problem Description

In Section 7.1.1, we analyzed the problem of the personnel information management system that had been discussed in Chapters 4.6. In Section 7.1.3 we proposed an improvement of the program. Here, we implement the salary calculating function mentioned in Section 7.1.1 using class inheritance and derivation. For example, we assume the salary calculating method to be: a manager’s fixed salary is 8,000 RMB; a part-time technician gets his salary by the hour and the hourly pay is 100 RMB; a part-time salesman’s salary includes commissions, which are 4% of his sales; a sales manager takes both his fixed salary of 5,000 RMB and sales commissions, which are 5% of his department’s sales in a current month.

7.7.2Class Design

According to the above needs, we design a base class employee, from which we derive the classes technician (part-time technician), manager (manager), and salesman (part-time salesman). Because a sales manager is both a manager and a salesman, we design the class salesmanager derived from both class manager and salesman for this group of people.

In the base class employee, aside from the constructor and destructor, the operations on information of all kinds of people should also be defined uniformly, to regulate the basic behaviors in all the derived classes of the class family. Since the salary calculation methods vary for different kinds of people, they cannot be defined uniformly in the base class employee. Therefore, we can set the function body of salary calculating in the base class as empty, and implement specific functionalities in the homonymous functions in different derived classes according to the hiding rule. The class design is shown in Figure 7.10.

Since the two base classes of the class salesmanager have a common base class employee, we design the class employee as virtual here to avoid ambiguity.

Fig. 7.10: UML of personnel information management system of a small corporation.

7.7.3Source Code and Explanation

Example 7.10: Personnel information management system.

The entire program is divided into three files: employee.h is a header file for class definitions; employee.cpp is a file in which function members of the classes are implemented; and 7_10.cpp contains the main function main(). After compiling, 7_10.cpp and employee.cpp are linked together. In the VC++ development environment, they should be placed in one project.

7.7.4Execution Result and Analysis

The execution result is as follows.

In the above program, each derived class only defines its own new members, while retaining the members inherited from the base class without change. The derived-class constructor only needs to initialize new members. When creating a derived-class object, the system will first call the base-class constructor to initialize the members inherited from the base class and then call the derived-class constructor to initialize new data members.

Each derived class has the function pay(). In main(), when calling the function pay() through a derived-class object, according to the hiding rule, the system will call the function declared in the derived class. The empty function in the base class only regulates the basic behaviors of the derived-classes in the class family.

The employee number starts from 1,000 and increases by 1 each time. The static data member employeeNo is initialized outside the class and is shared by all objects of the entire class family.

The program has two shortcomings:

Firstly, the function fun() declared in the base class is to be empty, but we still need to write the empty function body as its implementation, which seems redundant. However, we should keep it to uniformly regulate the basic behaviors of the class family. This problem can be solved by using virtual functions, whichwill be introduced in the next chapter.

Secondly, in main(), four objects of different classes are created. To perform similar operations on these objects (e.g., input name, output basic personnel information) we have to write similar statements four times. Can we use the base-class pointer array to point to multiple derived-class objects and deal with the objects in a loop structure, according to the type compatible rule? From what we have learned so far, this is impossible since a base-class pointer can only point to base-class members, while here we need to access the pay() functions declared in different classes. Again, this problem can be solved by using the virtual functions introduced in the next chapter.

7.8Summary

In this chapter, we first introduced the concept of class inheritance. Class inheritance allows programmers to give more specific and detailed class definitions based on the characteristics of the original class. A new class is generated from the original class, i.e., the new class inherits the characteristics of the original class, or the original class derives the new class. The procedure of deriving a new class includes three steps: accepting members inherited from the original class, modifying existing members, and adding new members. We focus on the access controls of base-classmembers and the addition of constructors and destructors, under different kinds of derivations. We then discuss the unique identification and access of derived-class members in more complicated inheritance relationships. Finally, we discuss the use scope of derived-class objects. Two examples, using Gaussian elimination method to solve linear equations and a personnel information management system, are presented as a review and conclusion of the chapter.

Inheritance is to obtain features from one’s ancestor. Class inheritance refers to when a new class inherits characteristics from an existing class. Class derivation is the process of creating a new class from an existing class. A derived class can also serve as a base class to derive new classes, which form a class hierarchy. Class derivation is a process of evolution and development, i.e., through extension, modification, and specification to create a new class from an existing class. Class derivation creates a class family that has common characteristics, to realize code reuse. The inheritance and derivation mechanism greatly facilitates the development and improvement of existing programs.

In the class hierarchy, the top layer represents the most abstract and common concept. A lower layer keeps the characteristics of its upper layer, while having its own characteristics. In the class hierarchy, it is a specialization process from top to bottom and an abstraction process from bottom to top.

A new class can be derived in three steps: accepting base-class members, modifying base-classmembers, and adding new members. In C++ inheritance, a derived class inherits all the members of its base classes except for the constructor and destructor. Modifications to base-class members concern two issues. The first one is the access control of base-class members, which depends on the inheritance mode specified in the definition of the derived class. The other is the overriding of the data and function members of the base class. Adding new members in a derived class is the key to the inheritance and derivation mechanism, and it is crucial for the development of functionalities in derived classes. We can add appropriate data and function members to the derived class according to actual needs. The constructor and destructor of the base class cannot be inherited, so we should add a new constructor and destructor to the derived class to do specific initialization and cleanup work.

After discussing the derivation procedure, we focus on the issues of the identification and access of the members (includingmembers inherited from the base class and new members) of a derived class and its object. There are two problems to be solved in an accessing process. The first one is regarding unique identification and the other is regarding the access attributes, i.e., the visibility, of a class member. We introduce several methods, including the hiding rule, scope qualifier, and virtual base class, to solve these unique identification problems.

The type compatible rule is about the use scope of derived-class objects. After public derivation, a derived class has all the functionalities of its base class. We can use a derived class to substitute for the base class wherever the latter one is needed. This characteristic lays the foundation for polymorphism, which will be discussed in the next chapter.

Exercises

7.1 Compare the three inheritance modes: public, protected, and private.

7.2 What is the execution order of a derived-class constructor?

7.3 If member function fn1() declared in base class A has already been overridden in derived class B,while member function fn2() declared in A has not, then how does one call the base-class members functions fn1() and fn2() in a derived class, respectively?

7.4 What is a virtual base class? What can it do?

7.5 Define a base class Shape, from which to derive two classes Rectangle and Circle. Both of the derived classes use function GetArea() to calculate the area. Derive another class Square from Rectangle.

7.6 Define a class Mammal, which stands for mammals, from which to derive a new class Dog. Define an object of class Dog. Observe the calling orders of the constructors and the destructors of both the base class and derived class, respectively.

7.7 Define a base class and a derived class, and output some prompt messages in both constructors. Create a derived-class object and observe the execution order of the constructors.

7.8 Define a class Document that has a data member, name. Derive a new class Book from the Document class and add a new member, PageCount, in class Book.

7.9 Define a base class Base that has two public member functions, fn1() and fn2(). Then privately derive a class Derived. How does one call the base-class function fn1 through a derived object?

7.10 Define a class object that has a data member weight and some function members operating weight. Derive a class box from the class object. Add new data members Height and width, and some function members operating the two new members, to the derived class. Declare an object of class box. Observe the calling orders of constructors and destructors.

7.11 Define a base class BaseClass, and derive class DerivedClass from the class Base-Class. Class BaseClass has member functions fn1() and fn2(). Class DerivedClass also declare functions fn1() and fn2() in its definition. Declare an object of DerivedClass in main(). Call fn1() and fn2() through the object of DerivedClass, a BaseClass pointer, and a DerivedClass pointer respectively. Observe the execution results.

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

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