home account info subscribe login search My ITKnowledge FAQ/help site map contact us


 
Brief Full
 Advanced
      Search
 Search Tips
To access the contents, click the chapter and section titles.

C++ in Plain English
(Publisher: IDG Books Worldwide, Inc.)
Author(s): Brian Overland
ISBN: 1558284729
Publication Date: 04/01/96

Bookmark It

Search this book:
 
Previous Table of Contents Next


The Advantage of Virtual Menu Commands

Given the way this code is written, the use of virtual functions is clearly required. But does it have to be written this way? What was gained by designing a program-menu system based on virtual functions?

Certainly, you can write this menu program using traditional programming techniques. But in the traditional approach, more control must be placed in the main loop. For example, rather than call Do_Command through the current object, code inside the main loop would have to test the value of the selection and then take a different action depending on the value:

switch (sel) {
   case 1:
       Bell_Do_Command();
       break;
case 2:
     Saying_Do_Command();
     break;
case 3:
     Add_Do_Command();
     break;
};

Every time the menu structure of the program changed, this code would have to be revised, as would any place in the program that activated a menu command. The program is easier to maintain using the virtual-function approach, which requires only one statement to activate a selected menu command:

commands[sel]->Do_Command();

The main point is not that the traditional approach necessarily requires much more coding overall or even that it is less elegant (although it is). The main point is that the traditional approach centralizes all control inside the main loop. With the virtual-function approach, the code in MAIN.CPP never needs to be recompiled. Future menu commands can be added without having to rewrite or rebuild the main program.

And there are other advantages to the virtual-function approach. The menu has to be initialized somewhere; in this case, it is initialized in the Init_Commands function. But once the virtual-function framework is in place, you can use any initialization technique. For example, you could initialize the menu from a data file. (In Windows programming, the build process does something like that by using the resource compiler to build menus.)

With the virtual-function approach, the menu can even change during run time. For example, because of a condition determined during running, some menu items may no longer apply, or new ones may be needed. (Look at how the File menu of Microsoft Word and other popular applications change during run time.) The virtual-function approach is much more dynamic; it can revise the menu structure at any time. With the switch statement used in the traditional approach, the menu structure is hard-coded. In other words, it is firmly fixed in the flow of control of the program and cannot be changed after the program is compiled.

Most object-oriented aspects of C++ can be simulated In C, although the C implementation is usually clumsier and requires additional work. This is true in the case of virtual functions. You can simulate the virtual-function capability through the use of callback functions. A call-back is a function whose address you pass to the main program or a specialized routine, which can then call the function you have written.
You could use callbacks instead of virtual functions, but you would need to write a callback function for each menu command and then initialize an array of structures specifying menu titles and callback function addresses. The virtual-function approach involves slightly less work and provides a more coherent, easy-to-read structure for this same program mechanism.
The section “How Virtual Functions Are Implemented” further describes the connections between virtual functions and callback functions.

Functions with No Implementation (Pure Virtual Functions)

The Do_Command function is an example of a pure virtual function. The characters =0 indicate that Do_Command has no implementation in this class—that is, there is no function definition for CMenu::Do_Command. However, classes derived from CMenu can implement Do_Command.

class CMenu {
public:
    char title[81];
    virtual void Do_Command(void) = 0;
};

Any class that has at least one pure virtual function is an abstract base class. The CMenu class is an abstract base class, because Do_Command is a pure virtual function. As an abstract base class, CMenu has an important limitation: it cannot be directly used to define (instantiate) an object. You can, however, declare pointers to CMenu.

CMenu *pMenu;         // OK
CMenu menu_thing;     // Error! Cannot instantiate

If abstract base classes cannot be instantiated, of what use are they? Abstract base classes are useful as general patterns for classes. You can use abstract base classes to create generalized interfaces, which the derived classes then implement. The CMenu class is a good, albeit simple, example of this approach.

The CMenu class could have implemented Do_Command by having it do nothing:

// CMENU.H - declaration of the CMenu base class

class CMenu {
public:
   char title[81];
   virtual void Do_Command(void);
};

// MENU.CPP - implementation of Cmenu

CMenu::Do_Command(void) {
}

However, this involves needless extra work, because there was never any intention of instantiating CMenu directly or calling CMenu::Do_Command.

How Virtual Functions Are Implemented

For the most part, I'm don't focus on how C++ is implemented in this book. Sometimes there is more than one way for a compiler to implement a specific feature of C++, and you don't usually need to understand underlying compiler implementation to understand object orientation and C++.

However, C programmers do tend to worry about program efficiency and trade-offs between speed and size—and C++ programmers tend to have the same mind-set. The speed and size penalties involved with virtual functions are worth understanding. In the simple case (no multiple inheritance), the implementation of virtual functions is relatively straightforward and standard from one C++ compiler to the next.

As I hinted earlier in a note, virtual functions are closely related to callback functions. Both mechanisms take advantage of the processor's ability to make indirect calls, which are function calls through a pointer. For example, the following code uses C syntax to call the function Hello indirectly.

int Hello(int n) {
    printf(“Hello, your lucky number is %d. \n”, n);
}

int *pFunction;

pFunction = &Hello;
(*pFunction) (5); // Call Hello through pFunction

The virtual-function capability adds several things to this indirect-call capability: it expresses indirect calls in terms of member functions, it builds tables of function pointers for each class, and it hides the indirect-call syntax so that calls to virtual functions look just like calls to any member function. In some cases, if you are writing a derived class, you may even have forgotten that a particular function is virtual. But with C++, the fact that a function is virtual makes no difference in the way you write it or call it.

All these things are ultimately programming conveniences, but that's not to say they aren't important. By making indirect function calls easier to use, the virtual-function capability encourages you to use them. It also provides a framework (class hierarchies) that gives them meaningful structure and context.

For each class that has any virtual functions, C++ builds a table of function pointers for that class. Each entry in the table contains the address of a virtual function. For example, consider the following class:

class CShape {
public:
   virtual int SetPoints(double ptArray[]);
   virtual void DrawMe(void);
   virtual void Move(double x, double y);
};

In the CShape class, the three functions SetPoints, DrawMe, and Move are good candidates for being made virtual, because they will probably be overridden in classes derived from CShape.

The C++ compiler constructs a table of function pointers for the CShape class. Notably, it does not construct such a table for each individual CShape object; one table for the entire class is enough, because all objects of the same class share all member functions.

This table is the virtual function table, or vtable, for the class. It has a pointer to the definition of each virtual function. If there are any nonvirtual functions in the class declaration, the nonvirtual functions do not appear in this table. There is no reason that they should; nonvirtual functions can be handled as normal function calls.

Figure 9.1 shows what the function table for CShape looks like.


Figure 9.1  Virtual function table for CShape.

Any number of other classes can be derived from CShape, and all these classes implement their own versions of these three functions. (Such classes can also add other virtual functions, but those functions are added to the end of the vtable.) At run time, how does an object “know” that it is an object of type CShape, for example, as opposed to some other class derived from CShape?

The answer is that each individual object has a hidden data member that points to the vtable for the class. (Note that if the object's class has no virtual functions, then the compiler does not need to add this member.) This hidden pointer, which we can name pVtable, is placed at the beginning of the object. In reality, the object doesn't know its type and doesn't need to. The program code simply makes an indirect function call through the pointer, and the appropriate function code is called automatically. Each object's pVtable member points to the vtable for its class (CCircle and CSquare, for example, would each have its own vtable), so the object is linked to all the function code for its class.

Figure 9.2 illustrates the structure of a CShape object. You can see how an indirect function call through pVtable results in a call to function implementations for the class.


Figure 9.2  A CShape object and the virtual function table (vtable)

Consider a call to a virtual function through an object, myshape:

CShape myshape, pShape;
pShape = &myshape;
pShape->DrawMe();

The C++ compiler translates the call to DrawMe into the following indirect function call:

(*(pShape->pVtable->DrawMe)) ();

This entire virtual-function mechanism can certainly be done in standard C. However, you would have to carry out the following steps:

1.  For each class, declare a structure consisting of a function pointer for each virtual function.
2.  Initialize each member of this structure so that it points to the appropriate function.
3.  Alter the class declaration so that it includes an additional member, pVtable.
4.  Initialize pVtable to point to the class virtual function table.
5.  Translate all virtual-function calls, as shown in the previous code fragment.

None of this is impossible to do. But, clearly, C++ is much more convenient because it automates all this work for you.


Previous Table of Contents Next
[an error occurred while processing this directive]

Products |  Contact Us |  About Us |  Privacy  |  Ad Info  |  Home

Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.