Derivation is often represented graphically by a pointer from the derived class to its base class indicating that the derived class refers to its base (rather than the other way around):
Employee Manager
.
A derived class is often said to inherit properties from its base, so the relationship is also called inheritance. A base class is sometimes called a superclass and a derived class a subclass. This terminology, however, is confusing to people who observe that the data in a derived class object is a superset of the data of an object of its base class. A derived class is larger than its base class in the sense that it holds more data and provides more functions.
A popular and efficient implementation of the notion of derived classes has an object of the derived class represented as an object of the base class, with the information belonging specifically to the derived class added at the end.
In general, if a class Derived has a public base class Base, then a Derived* can be assigned to a variable of type Base* without the use of explicit type conversion. The opposite conversion, from Base* to Derived*, must be explicit.
In other words, an object of a derived class can be treated as an object of its base class when manipulated through pointers and references. The opposite is not true.
Using a class as a base is equivalent to declaring an (unnamed) object of that class. Consequently, a class must be defined in order to be used as a base.
Member Functions
A member of a derived class can use the public – and protected – members of its base class as if they were declared in the derived class itself. However, a derived class cannot use a base class’ private names.
This comes as a surprise to some, but consider the alternative: that a member function of a derived class could access the private members of its base class. The concept of a private member would be rendered meaningless by allowing a programmer to gain access to the private part of a class simply by deriving a new class from it. Furthermore, one could no longer find all uses of a private name by looking at the functions declared as members and friends of that class. One would have to examine every source file of the complete program for derived classes, then examine every function of those classes, then find every class derived from those classes, etc. This is, at best, tedious and often impractical. Where it is acceptable, protected – rather than private – members can be used. A protected member is like a public member to a member of a derived class, yet it is like a private member to other functions.
Constructors and Destructors
Some derived classes need constructors. If a base class has constructors, then a constructor must be invoked. Default constructors can be invoked implicitly. However, if all constructors for a base require arguments, then a constructor for that base must be explicitly called.
Arguments for the base class’ constructor are specified in the definition of a derived class’ constructor. In this respect, the base class acts exactly like a member of the derived class
A derived class constructor can specify initializers for its own members and immediate bases only. It cannot directly initialize members of a base.
Class objects are constructed from the bottom up: first the base, then the members, and then the derived class itself. They are destroyed in the opposite order: first the derived class itself, then the members, and then the base. Members and bases are constructed in order of declaration in the class and destroyed in the reverse order
Copying
Copying of class objects is defined by the copy constructor and assignments.
Because the Employee copy functions do not know anything about Managers, only the Employee part of a Manager is copied. This is commonly referred to as slicing and can be a source of surprises and errors. One reason to pass pointers and references to objects of classes in a hierarchy is to avoid slicing. Other reasons are to preserve polymorphic behavior and to gain efficiency.
Class Hierarchies
A derived class can itself be a base class. Such a set of related classes is traditionally called a class hierarchy. Such a hierarchy is most often a tree, but it can also be a more general graph structure.
Type Fields
To use derived classes as more than a convenient shorthand in declarations, we must solve the following problem: Given a pointer of type base*, to which derived type does the object pointed to really belong? There are four fundamental solutions to the problem:
[1] Ensure that only objects of a single type are pointed to.
[2] Place a type field in the base class for the functions to inspect.
[3] Usedynamiccast.
[4] Use virtual functions.
Pointers to base classes are commonly used in the design of container classes such as set, vector, and list. In this case, solution 1 yields homogeneous lists, that is, lists of objects of the same type. Solutions 2, 3, and 4 can be used to build heterogeneous lists, that is, lists of (pointers to) objects of several different types. Solution 3 is a language supported variant of solution 2. Solution 4 is a special type safe variation of solution 2. Combinations of solutions 1 and 4 are particularly interesting and powerful; in almost all situations, they yield cleaner code than do solutions 2 and 3.
switch (e->type)
{
case Employee: :E:
break;
case Employee: :M:
break;
}
Type field has the fundamental weakness in that it depends on the programmer manipulating types in a way that cannot be checked by the compiler.
Use of a type field is an error prone technique that leads to maintenance problems. The problems increase in severity as the size of the program increases because the use of a type field causes a violation of the ideals of modularity and data hiding. Each function using a type field must know about the representation and other details of the implementation of every class derived from the one containing the type field.
It also seems that the existence of any common data accessible from every derived class, such as a type field, tempts people to add more such data. The common base thus becomes the repository of all kinds of ‘‘useful information.’’ This, in turn, gets the implementation of the base and derived classes intertwined in ways that are most undesirable. For clean design and simpler maintenance, we want to keep separate issues separate and avoid mutual dependencies.
Virtual Functions
Virtual functions overcome the problems with the type field solution by allowing the programmer to declare functions in a base class that can be redefined in each derived class. The compiler and loader will guarantee the correct correspondence between objects and the functions applied to them.
To allow a virtual function declaration to act as an interface to functions defined in derived classes, the argument types specified for a function in a derived class cannot differ from the argument types declared in the base, and only very slight changes are allowed for the return type. A virtual member function is sometimes called a method.
A virtual function must be defined for the class in which it is first declared (unless it is declared to be a pure virtual function)
A virtual function can be used even if no class is derived from its class, and a derived class that does not need its own version of a virtual function need not provide one. When deriving a class, simply provide an appropriate function, if it is needed.
A function from a derived class with the same name and the same set of argument types as a virtual function in a base is said to override the base class version of the virtual function. Except where we explicitly say which version of a virtual function is called (as in the call Employee::print()), the overriding function is chosen as the most appropriate for the object for which it is called.
Getting ‘‘the right’’ behavior from Employee’s functions independently of exactly what kind of Employee is actually used is called polymorphism. A type with virtual functions is called a polymorphic type. To get polymorphic behavior in C++, the member functions called must be virtual and objects must be manipulated through pointers or references. When manipulating an object directly (rather than through a pointer or reference), its exact type is known by the compilation so that runtime polymorphism is not needed.
Clearly, to implement polymorphism, the compiler must store some kind of type information in each object of class Employee and use it to call the right version of the virtual function print(). In a typical implementation, the space taken is just enough to hold a pointer. This space is taken only in objects of a class with virtual functions – not in every object, or even in every object of a derived class. You pay this overhead only for classes for which you declare virtual functions.
Had you chosen to use the alternative type field solution, a comparable amount of space would have been needed for the type field.
Calling a function using the scope resolution operator :: as is done in Manager::print() ensures that the virtual mechanism is not used.
The use of a qualified name has another desirable effect. That is, if a virtual function is also inline (as is not uncommon), then inline substitution can be used for calls specified using ::. This provides the programmer with an efficient way to handle some important special cases in which one virtual function calls another for the same object.
It is worth remembering that the traditional and obvious implementation of a virtual function call is simply an indirect function call, so efficiency concerns should not deter anyone from using a virtual function where an ordinary function call would be acceptably efficient.
Abstract Classes
A class with one or more pure virtual functions is an abstract class, and no objects of that abstract class can be created. An abstract class can be used only as an interface and as a base for other classes.
A pure virtual function that is not defined in a derived class remains a pure virtual function, so the derived class is also an abstract class. This allows us to build implementations in stages.
An important use of abstract classes is to provide an interface without exposing any implementation details.
With the introduction of abstract classes, we have the basic facilities for writing a complete program in a modular fashion using classes as building blocks.
Design of Class Hierarchies
Experience shows that novice programmers tend to mess with protected data in ways that are unnecessary and that cause maintenance problems. Data is better kept private so that writers of derived classes cannot mess with them. Better still, data should be in the derived classes, where it can be defined to match requirements exactly and cannot complicate the life of unrelated derived classes. In almost all cases, a protected interface should contain only functions, types, and constants.
Class Hierarchies and Abstract Classes
An abstract class is an interface. A class hierarchy is a means of building classes incrementally. Naturally, every class provides an interface to users and some abstract classes provide significant functionality to build from, but ‘‘interface’’ and ‘‘building block’’ are the primary roles of abstract classes and class hierarchies.
A classical hierarchy is a hierarchy in which the individual classes both provide useful functionality for users and act as building blocks for the implementation of more advanced or specialized classes. Such hierarchies are ideal for supporting programming by incremental refinement. They provide the maximum support for the implementation of new classes as long as the new class relates strongly to the existing hierarchy.
Classical hierarchies do tend to couple implementation concerns rather strongly with the interfaces provided to users. Abstract classes can help here. Hierarchies of abstract classes provide a clean and powerful way of expressing concepts without encumbering them with implementation concerns or significant runtime overheads. After all, a virtual function call is cheap and independent of the kind of abstraction barrier it crosses. It costs no more to call a member of an abstract class than to call any other virtual function.
The logical conclusion of this line of thought is a system represented to users as a hierarchy of abstract classes and implemented by a classical hierarchy.
Advice
[1] Avoid type fields.
[2] Use pointers and references to avoid slicing.
[3] Use abstract classes to focus design on the provision of clean interfaces.
[4] Use abstract classes to minimize interfaces.
[5] Use abstract classes to keep implementation details out of interfaces.
[6] Use virtual functions to allow new implementations to be added without affecting user code.
[7] Use abstract classes to minimize recompilation of user code.
[8] Use abstract classes to allow alternative implementations to coexist.
[9] A class with a virtual function should have a virtual destructor.
[10] An abstract class typically doesn’t need a constructor.
[11] Keep the representations of distinct concepts distinct.
Employee Manager
.
A derived class is often said to inherit properties from its base, so the relationship is also called inheritance. A base class is sometimes called a superclass and a derived class a subclass. This terminology, however, is confusing to people who observe that the data in a derived class object is a superset of the data of an object of its base class. A derived class is larger than its base class in the sense that it holds more data and provides more functions.
A popular and efficient implementation of the notion of derived classes has an object of the derived class represented as an object of the base class, with the information belonging specifically to the derived class added at the end.
In general, if a class Derived has a public base class Base, then a Derived* can be assigned to a variable of type Base* without the use of explicit type conversion. The opposite conversion, from Base* to Derived*, must be explicit.
In other words, an object of a derived class can be treated as an object of its base class when manipulated through pointers and references. The opposite is not true.
Using a class as a base is equivalent to declaring an (unnamed) object of that class. Consequently, a class must be defined in order to be used as a base.
Member Functions
A member of a derived class can use the public – and protected – members of its base class as if they were declared in the derived class itself. However, a derived class cannot use a base class’ private names.
This comes as a surprise to some, but consider the alternative: that a member function of a derived class could access the private members of its base class. The concept of a private member would be rendered meaningless by allowing a programmer to gain access to the private part of a class simply by deriving a new class from it. Furthermore, one could no longer find all uses of a private name by looking at the functions declared as members and friends of that class. One would have to examine every source file of the complete program for derived classes, then examine every function of those classes, then find every class derived from those classes, etc. This is, at best, tedious and often impractical. Where it is acceptable, protected – rather than private – members can be used. A protected member is like a public member to a member of a derived class, yet it is like a private member to other functions.
Constructors and Destructors
Some derived classes need constructors. If a base class has constructors, then a constructor must be invoked. Default constructors can be invoked implicitly. However, if all constructors for a base require arguments, then a constructor for that base must be explicitly called.
Arguments for the base class’ constructor are specified in the definition of a derived class’ constructor. In this respect, the base class acts exactly like a member of the derived class
A derived class constructor can specify initializers for its own members and immediate bases only. It cannot directly initialize members of a base.
Class objects are constructed from the bottom up: first the base, then the members, and then the derived class itself. They are destroyed in the opposite order: first the derived class itself, then the members, and then the base. Members and bases are constructed in order of declaration in the class and destroyed in the reverse order
Copying
Copying of class objects is defined by the copy constructor and assignments.
Because the Employee copy functions do not know anything about Managers, only the Employee part of a Manager is copied. This is commonly referred to as slicing and can be a source of surprises and errors. One reason to pass pointers and references to objects of classes in a hierarchy is to avoid slicing. Other reasons are to preserve polymorphic behavior and to gain efficiency.
Class Hierarchies
A derived class can itself be a base class. Such a set of related classes is traditionally called a class hierarchy. Such a hierarchy is most often a tree, but it can also be a more general graph structure.
Type Fields
To use derived classes as more than a convenient shorthand in declarations, we must solve the following problem: Given a pointer of type base*, to which derived type does the object pointed to really belong? There are four fundamental solutions to the problem:
[1] Ensure that only objects of a single type are pointed to.
[2] Place a type field in the base class for the functions to inspect.
[3] Usedynamiccast.
[4] Use virtual functions.
Pointers to base classes are commonly used in the design of container classes such as set, vector, and list. In this case, solution 1 yields homogeneous lists, that is, lists of objects of the same type. Solutions 2, 3, and 4 can be used to build heterogeneous lists, that is, lists of (pointers to) objects of several different types. Solution 3 is a language supported variant of solution 2. Solution 4 is a special type safe variation of solution 2. Combinations of solutions 1 and 4 are particularly interesting and powerful; in almost all situations, they yield cleaner code than do solutions 2 and 3.
switch (e->type)
{
case Employee: :E:
break;
case Employee: :M:
break;
}
Type field has the fundamental weakness in that it depends on the programmer manipulating types in a way that cannot be checked by the compiler.
Use of a type field is an error prone technique that leads to maintenance problems. The problems increase in severity as the size of the program increases because the use of a type field causes a violation of the ideals of modularity and data hiding. Each function using a type field must know about the representation and other details of the implementation of every class derived from the one containing the type field.
It also seems that the existence of any common data accessible from every derived class, such as a type field, tempts people to add more such data. The common base thus becomes the repository of all kinds of ‘‘useful information.’’ This, in turn, gets the implementation of the base and derived classes intertwined in ways that are most undesirable. For clean design and simpler maintenance, we want to keep separate issues separate and avoid mutual dependencies.
Virtual Functions
Virtual functions overcome the problems with the type field solution by allowing the programmer to declare functions in a base class that can be redefined in each derived class. The compiler and loader will guarantee the correct correspondence between objects and the functions applied to them.
To allow a virtual function declaration to act as an interface to functions defined in derived classes, the argument types specified for a function in a derived class cannot differ from the argument types declared in the base, and only very slight changes are allowed for the return type. A virtual member function is sometimes called a method.
A virtual function must be defined for the class in which it is first declared (unless it is declared to be a pure virtual function)
A virtual function can be used even if no class is derived from its class, and a derived class that does not need its own version of a virtual function need not provide one. When deriving a class, simply provide an appropriate function, if it is needed.
A function from a derived class with the same name and the same set of argument types as a virtual function in a base is said to override the base class version of the virtual function. Except where we explicitly say which version of a virtual function is called (as in the call Employee::print()), the overriding function is chosen as the most appropriate for the object for which it is called.
Getting ‘‘the right’’ behavior from Employee’s functions independently of exactly what kind of Employee is actually used is called polymorphism. A type with virtual functions is called a polymorphic type. To get polymorphic behavior in C++, the member functions called must be virtual and objects must be manipulated through pointers or references. When manipulating an object directly (rather than through a pointer or reference), its exact type is known by the compilation so that runtime polymorphism is not needed.
Clearly, to implement polymorphism, the compiler must store some kind of type information in each object of class Employee and use it to call the right version of the virtual function print(). In a typical implementation, the space taken is just enough to hold a pointer. This space is taken only in objects of a class with virtual functions – not in every object, or even in every object of a derived class. You pay this overhead only for classes for which you declare virtual functions.
Had you chosen to use the alternative type field solution, a comparable amount of space would have been needed for the type field.
Calling a function using the scope resolution operator :: as is done in Manager::print() ensures that the virtual mechanism is not used.
The use of a qualified name has another desirable effect. That is, if a virtual function is also inline (as is not uncommon), then inline substitution can be used for calls specified using ::. This provides the programmer with an efficient way to handle some important special cases in which one virtual function calls another for the same object.
It is worth remembering that the traditional and obvious implementation of a virtual function call is simply an indirect function call, so efficiency concerns should not deter anyone from using a virtual function where an ordinary function call would be acceptably efficient.
Abstract Classes
A class with one or more pure virtual functions is an abstract class, and no objects of that abstract class can be created. An abstract class can be used only as an interface and as a base for other classes.
A pure virtual function that is not defined in a derived class remains a pure virtual function, so the derived class is also an abstract class. This allows us to build implementations in stages.
An important use of abstract classes is to provide an interface without exposing any implementation details.
With the introduction of abstract classes, we have the basic facilities for writing a complete program in a modular fashion using classes as building blocks.
Design of Class Hierarchies
Experience shows that novice programmers tend to mess with protected data in ways that are unnecessary and that cause maintenance problems. Data is better kept private so that writers of derived classes cannot mess with them. Better still, data should be in the derived classes, where it can be defined to match requirements exactly and cannot complicate the life of unrelated derived classes. In almost all cases, a protected interface should contain only functions, types, and constants.
Class Hierarchies and Abstract Classes
An abstract class is an interface. A class hierarchy is a means of building classes incrementally. Naturally, every class provides an interface to users and some abstract classes provide significant functionality to build from, but ‘‘interface’’ and ‘‘building block’’ are the primary roles of abstract classes and class hierarchies.
A classical hierarchy is a hierarchy in which the individual classes both provide useful functionality for users and act as building blocks for the implementation of more advanced or specialized classes. Such hierarchies are ideal for supporting programming by incremental refinement. They provide the maximum support for the implementation of new classes as long as the new class relates strongly to the existing hierarchy.
Classical hierarchies do tend to couple implementation concerns rather strongly with the interfaces provided to users. Abstract classes can help here. Hierarchies of abstract classes provide a clean and powerful way of expressing concepts without encumbering them with implementation concerns or significant runtime overheads. After all, a virtual function call is cheap and independent of the kind of abstraction barrier it crosses. It costs no more to call a member of an abstract class than to call any other virtual function.
The logical conclusion of this line of thought is a system represented to users as a hierarchy of abstract classes and implemented by a classical hierarchy.
Advice
[1] Avoid type fields.
[2] Use pointers and references to avoid slicing.
[3] Use abstract classes to focus design on the provision of clean interfaces.
[4] Use abstract classes to minimize interfaces.
[5] Use abstract classes to keep implementation details out of interfaces.
[6] Use virtual functions to allow new implementations to be added without affecting user code.
[7] Use abstract classes to minimize recompilation of user code.
[8] Use abstract classes to allow alternative implementations to coexist.
[9] A class with a virtual function should have a virtual destructor.
[10] An abstract class typically doesn’t need a constructor.
[11] Keep the representations of distinct concepts distinct.
No comments:
Post a Comment