Implementation DigressionObviously, for polymorphism to work, the object itself must store some information about its actual type. Based on this type the call is dispatched to the appropriate implementation of the method. In fact, the object may store a pointer to a dispatch table through which virtual methods are called. And that's how it works. Every class that has at least one virtual method (a polymorphic class) has a hidden member that is a pointer to a virtual table. The virtual table contains the addresses of the virtual member functions for that particular class. When a call is made through a pointer or reference to such object, the compiler generates code that dereferences the pointer to the object’s virtual table and makes an indirect call using the address of a member function stored in the table.
Figure. The invisible member of a celestial body—the pointer to the vtable. The first entry in the vtable is a pointer to a function that is the destructor of celestial body. An object of a derived class that has overridden the implementation of the virtual method has its pointer pointed to a different virtual table. In that table the entry corresponding to that particular method contains the address of the overridden member function.
Figure 2-2 The vtable pointer of a star points to a different vtable. Its first entry is also a pointer to a function that serves as a destructor. Only this time it’s the destructor of a star. Notice that, based on the type of the pointer, the compiler has enough information to decide whether to generate an indirect call through a vtable, or a direct call or inline expansion. If the pointer is declared to point to a class that has virtual functions, and the call is made to one of these functions, an indirect call is automatically generated. Otherwise a direct call or inline expansion is done. In any case, the compiler must first see the declaration of the base class (presumably in one of the header files) to know which functions are virtual and which are not. The OverheadBefore you decide to use virtual functions everywhere without much thinking, I have to make you aware of the size and speed considerations. The space overhead of polymorphism is: one pointer per every instance of the class. There is an additional per-class overhead of a vtable, but it’s not that important. One more pointer per object is not a lot when dealing with a small number of relatively large objects. It becomes significant, however, when dealing with a large number of lightweight objects. A virtual function call is slower than a direct call and significantly slower than the use of an inline function. Again, you can safely ignore this overhead when calling a heavy-weight member function, But turning an inline function such as AtEnd () into a virtual function may significantly slow down your loops. So don’t even think of the idea of creating the class Object—the mother of all objects—with a handy virtual destructor (and maybe one more integer field for some kind of a class ID [run-time typing!], plus some conditionally compiled debugging devices, etc.). Don’t try to make it the root of all classes. In fact, if you hear somebody complaining about how slow C++ is, he or she is probably a victim of this Smalltalk syndrome in C++. Not that Smalltalk is a poor language. When size and speed are of no concern, Smalltalk beats C++ on almost all fronts. It is a truly object oriented language with no shameful heritage of the hackers’ C. It unifies built-in types with user defined types much better than C++. It has a single-rooted hierarchy of objects. All methods are virtual (you can even override them at runtime!). Java, on the other hand, tries to strike a balance between "objectivity" and performance. In Java all methods are virtual, except when explicitly declared final (and then they cannot be overridden). If speed and size are of concern to you, stick to C++ and use polymorphism wisely. Well designed polymorphic classes will lead to C++ code that is as fast as the equivalent C code (and much better from the maintenance point of view). This is possible when polymorphism is used to reduce a string of conditionals (or a switch statement) to a single virtual function call. The rule of thumb for those coming from the C background is to be on the lookout for the switch statements and complicated conditionals. It is natural in C++ to use polymorphism in their place. |