Section 15.1. OOP: An Overview


15.1. OOP: An Overview

The key idea behind OOP is polymorphism. Polymorphism is derived from a Greek word meaning "many forms." We speak of types related by inheritance as polymorphic types, because in many cases we can use the "many forms" of a derived or base type interchangeably. As we'll see, in C++, polymorphism applies only to references or pointers to types related by inheritance.

Inheritance

Inheritance lets us define classes that model relationships among types, sharing what is common and specializing only that which is inherently different. Members defined by the base class are inherited by its derived classes. The derived class can use, without change, those operations that do not depend on the specifics of the derived type. It can redefine those member functions that do depend on its type, specializing the function to take into account the peculiarities of the derived type. Finally, a derived class may define additional members beyond those it inherits from its base class.

Classes related by inheritance are often described as forming an inheritance hierarchy. There is one class, referred to as the root, from which all the other classes inherit, directly or indirectly. In our bookstore example, we will define a base class, which we'll name Item_base, to represent undiscounted books. From Item_base we will inherit a second class, which we'll name Bulk_item, to represent books sold with a quantity discount.

At a minimum, these classes will define the following operations:

  • an operation named book that will return the ISBN

  • an operation named net_price that returns the price for purchasing a specified number of copies of a book

Classes derived from Item_base will inherit the book function without change: The derived classes have no need to redefine what it means to fetch the ISBN. On the other hand, each derived class will need to define its own version of the net_price function to implement an appropriate discount pricing strategy.

In C++, a base class must indicate which of its functions it intends for its derived classes to redefine. Functions defined as virtual are ones that the base expects its derived classes to redefine. Functions that the base class intends its children to inherit are not defined as virtual.

Given this discussion, we can see that our classes will define three (const) member functions:

  • A nonvirtual function, std::string book(), that returns the ISBN. It will be defined by Item_base and inherited by Bulk_item.

  • Two versions of the virtual function, double net_price(size_t), to return the total price for a given number of copies of a specific book. Both Item_base and Bulk_item will define their own versions of this function.

Dynamic Binding

Dynamic binding lets us write programs that use objects of any type in an inheritance hierarchy without caring about the objects' specific types. Programs that use these classes need not distinguish between functions defined in the base or in a derived class.

For example, our bookstore application would let a customer select several books in a single sale. When the customer was done shopping, the application would calculate the total due. One part of figuring the final bill would be to print for each book purchased a line reporting the total quantity and sales price for that portion of the purchase.

We might define a function named print_total to manage this part of the application. The print_total function, given an item and a count, should print the ISBN and the total price for purchasing the given number of copies of that particular book. The output of this function should look like:

      ISBN: 0-201-54848-8 number sold: 3 total price: 98      ISBN: 0-201-82470-1 number sold: 5 total price: 202.5 

Our print_total function might look something like the following:

      // calculate and print price for given number of copies, applying any discounts      void print_total(ostream &os,                       const Item_base &item, size_t n)      {           os << "ISBN: " << item.book() // calls Item_base::book              << "\tnumber sold: " << n << "\ttotal price: "              // virtual call: which version of net_price to call is resolved at run time              << item.net_price(n) << endl;      } 

The function's work is trivial: It prints the results of calling book and net_price on its item parameter. There are two interesting things about this function.

First, even though its second parameter is a reference to Item_base, we can pass either an Item_base object or a Bulk_item object to this function.

Second, because the parameter is a reference and the net_price function is virtual, the call to net_price will be resolved at run time. The version of net_price that is called will depend on the type of the argument passed to print_total. When the argument to print_total is a Bulk_item, the version of net_price that is run will be the one defined in Bulk_item that applies a discount. If the argument is an Item_base object, then the call will be to the version defined by Item_base.

In C++, dynamic binding happens when a virtual function is called through a reference (or a pointer) to a base class. The fact that a reference (or pointer) might refer to either a base- or a derived-class object is the key to dynamic binding. Calls to virtual functions made through a reference (or pointer) are resolved at run time: The function that is called is the one defined by the actual type of the object to which the reference (or pointer) refers.





C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net