I l@ve RuBoard |
![]() ![]() |
5.2 A Tour of Object-Oriented ProgrammingLet's implement a simple three-level class hierarchy to introduce the C++ language constructs and programming idioms that support object-oriented programming. We root our class hierarchy with the abstract LibMat base class. We derive a Book class from LibMat and in turn derive an AudioBook class from Book. We limit our interface to a single function, print(), together with a constructor and destructor. I've instrumented each member function to output its presence so that we can trace the behavior of the program. By default, a member function is resolved statically at compile-time. To have a member function resolved dynamically during run-time, we preface its declaration with the virtual keyword. The LibMat class declares its destructor and print() to be virtual: class LibMat { public: LibMat(){ cout << "LibMat::LibMat() default constructor!\n"; } virtual ~LibMat(){ cout << "LibMat::~LibMat() destructor!\n"; } virtual void print() const { cout << "LibMat::print() -- I am a LibMat object!\n"; } }; Let's now define a nonmember print() function that takes as a single parameter a const LibMat reference: void print( const LibMat &mat ) { cout << "in global print(): about to print mat.print()\n"; // this resolves to a print() member function // based on the actual object mat refers to ... mat.print(); } Within our main() program, we repeatedly invoke print(), passing it, in turn, a LibMat class object, a Book class object, and an AudioBook class object as its parameter. Based on the actual object mat refers to within each invocation of print(), the appropriate LibMat, Book, or AudioBook print() member function is invoked. Our first invocation looks like this: cout << "\n" << "Creating a LibMat object to print()\n"; LibMat libmat; print( libmat ); Here is a trace of its execution: Creating a LibMat object to print() // the construction of Libmat libmat LibMat::LibMat() default constructor! // the handling of print( libmat ) in global print(): about to print mat.print() LibMat::print() -- I am a LibMat object! // the destruction of Libmat libmat LibMat::~LibMat() destructor! I hope there is nothing surprising in this. The definition of libmat is followed by the default constructor invocation. Within print(), mat.print() resolves to LibMat::print(). This is followed by the LibMat destructor invocation. Things become a bit more surprising when we pass print() a Book object: cout << "\n" << "Creating a Book object to print()\n"; Book b( "The Castle", "Franz Kafka" ); print( b ); Here is an annotated trace of the execution: Creating a Book object to print() // the construction of Book b LibMat::LibMat() default constructor! Book::Book( The Castle, Franz Kafka ) constructor // the handling of print( b ) in global print(): about to print mat.print() Book::print() -- I am a Book object! My title is: The Castle My author is: Franz Kafka // the destruction of Book b Book::~Book() destructor! LibMat::~LibMat() destructor! A first observation is that the virtual invocation through mat.print() actually works! The function invoked is Book::print() and not LibMat::print(). The second interesting thing is that both the base and the derived class constructors are invoked when the derived class object is defined. (When the derived class object is destroyed, both the derived and the base class destructors are invoked.) How do we actually implement the derived Book class? To indicate that our new class is inheriting from an existing class, we follow its name with a colon (:) followed by the public keyword [1] and the name of the base class:
class Book : public LibMat { public: Book( const string &title, const string &author ) : _title( title ), _author( author ){ cout << "Book::Book( " << _title << ", " << _author << " ) constructor\n"; } virtual ~Book(){ cout << "Book::~Book() destructor!\n"; } virtual void print() const { cout << "Book::print() -- I am a Book object!\n" << "My title is: " << _title << '\n' << "My author is: " << _author << endl; } const string& title() const { return _title; } const string& author() const { return _author; } protected: string _title; string _author; }; The print() instance within Book overrides the LibMat instance. This is the function invoked by mat.print(). The two access functions title() and author() are nonvirtual inline member functions. We haven't seen the protected keyword before. A member declared as protected can be directly accessed by the derived classes but cannot be directly accessed by the general program. Let's next derive a specialized AudioBook class from our Book class. An AudioBook, in addition to a title and author, has a narrator. Before we look at its implementation, let's first pass print() an AudioBook class object: cout << "\n" << "Creating an AudioBook object to print()\n"; AudioBook ab( "Man Without Qualities", "Robert Musil", "Kenneth Meyer" ); print( ab ); What should we expect from a trace of its execution? We should expect (1) that AudioBook::print() is invoked through mat.print() and (2) that ab is constructed by, in turn, the LibMat, Book, and AudioBook constructors. This is what the trace shows: Creating an AudioBook object to print() // the construction of AudioBook ab LibMat::LibMat() default constructor! Book::Book( Man Without Qualities, Robert Musil ) constructor AudioBook::AudioBook( Man Without Qualities, Robert Musil, Kenneth Meyer ) constructor // the resolution of print( ab ) in global print(): about to print mat.print() // oops: need to handle a Book and an AudioBook! AudioBook::print() -- I am a AudioBook object! My title is: Man Without Qualities My author is: Robert Musil My narrator is: Kenneth Meyer // the destruction of AudioBook ab AudioBook::~AudioBook() destructor! Book::~Book() destructor! LibMat::~LibMat() destructor! How do we implement the AudioBook derived class? We have only to program those aspects of an AudioBook that are different from those of its base Book class: the print() function, of course, support for the name of the AudioBook narrator, and the class constructor and destructor. The Book class data members and member functions that support the author and title can be used directly within the AudioBook class in the same way as if they had been defined within it rather than being inherited. class AudioBook : public Book { public: AudioBook( const string &title, const string &author, const string &narrator ) : Book( title, author ), _narrator( narrator ) { cout << "AudioBook::AudioBook( " << _title << ", " << _author << ", " << _narrator << " ) constructor\n"; } ~AudioBook() { cout << "AudioBook::~AudioBook() destructor!\n"; } virtual void print() const { cout << "AudioBook::print() -- I am an AudioBook object!\n" // note the direct access of the inherited // data members _title and _author << "My title is: " << _title << '\n' << "My author is: " << _author << '\n' << "My narrator is: " << _narrator << endl; } const string& narrator() const { return _narrator; } protected: string _narrator; }; Users of the derived class need not distinguish between inherited members and members actually defined within the derived class. The use of both is transparent: int main() { AudioBook ab( "Mason and Dixon", "Thomas Pynchon", "Edwin Leonard" ); cout << "The title is " << ab.title() << '\n' << "The author is " << ab.author() << '\n' << "The narrator is " << ab.narrator() << endl; } I hope that this section has given you a sense of how object-oriented programming is supported in C++. Literally, all that's missing is the details, which we deal with in the rest of the chapter. A good exercise at this point might be the following: (1) Download the source code from the Addison Wesley Longman site. (2) Look in the Chapter 5 directory for the LibMat class hierarchy and the main() program that exercises it. (3) Derive a Magazine class from LibMat and add an invocation of print(), passing in a Magazine class object. ![]() |
I l@ve RuBoard |
![]() ![]() |