course of C++ programming language

lecture 10: polymorphism and virtual functions


Introduction

Encapsulation creates new data types by combining characteristics and behaviors. Access control separates the interface from the implementation by making the details private. This kind of mechanical organization makes ready sense to someone with a procedural programming background. But virtual functions deal with decoupling in terms of types. On this lecture, you saw how inheritance allows the treatment of an object as its own type or its base type. This ability is critical because it allows many types (derived from the same base type) to be treated as if they were one type, and a single piece of code to work on all those different types equally. The virtual function allows one type to express its distinction from another, similar type, as long as they're both derived from the same base type. This distinction is expressed through differences in behavior of the functions that you can call through the base class.

Virtual functions

A virtual function is a member function that is declared within a base class and redefined by a derived class. To create a virtual function, precede the function's declaration in the base class with the keyword virtual. When a class containing a virtual function is inherited, the derived class redefines the virtual function to fit its own needs. The virtual function within the base class defines the form of the interface to that function. Each redefinition of the virtual function by a derived class implements its operation as it relates specifically to the derived class.

class Animal { public: // virtual member function virtual void move () { cout << "I am changing myself position" << endl; } // normal member function void name () { cout << "I am animal" << endl; } }; class Bird : public Animal { public: virtual void move () { cout << "I am flying" << endl; } }; class Fish : public Animal { public: virtual void move () { cout << "I am swimming" << endl; } }; class Cat : public Animal { public: virtual void move () { cout << "I am running" << endl; } }; class Cheetah : public Cat { public: virtual void move () { cout << "I am running very fast" << endl; } void name () { cout << "I am cheetah" << endl; } };

The compiler will guarantee the correct correspondence between objects and the member functions applied to them.

int main () { Animal *p; Animal a; Bird b; Cheetah c; // point to an object of the base class Animal p = &a; p->move(); // access Animal's move(): I am changing myself position p->name(); // access Animal's name(): I am animal // point to an object of derived class Bird p = &b; p->move(); // access Bird's move(): I am flying p->name(); // access Animal's name(): I am animal // point to an object of derived class Cheetah p = &c; p->move(); // access Cheetah's move(): I am running very fast p->name(); // access Animal's name(): I am animal Cheetah *pch = &c; p->move(); // access Cheetah's move(): I am running very fast p->name(); // access Cheetah's name(): I am cheetah return 0; }

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 Cat::move()), the overriding function is chosen as the most appropriate for the object for which it is called.

Getting "the right" behavior from Animal's functions independently of exactly what kind of Animal 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 run-time polymorphism is not needed.

Function call binding

Connecting a function call to a function body is called binding. When binding is performed before the program is run (by the compiler and linker), it's called early binding.

The binding occurs at runtime is called late binding (it base on the type of the object). Late binding is also called dynamic binding or runtime binding. When a language implements late binding, there must be some mechanism to determine the type of the object at runtime and call the appropriate member function. In the case of a compiled language, the compiler still doesn't know the actual object type, but it inserts code that finds out and calls the correct function body. The late-binding mechanism varies from language to language, but you can imagine that some sort of type information must be installed in the objects.

Clearly, to implement polymorphism, the compiler must store some kind of type information in each object of class Animal and use it to call the right version of the virtual function move(). 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.

Abstract classes

Some classes, such as class Shape represent abstract concepts for which objects cannot exist. A Shape makes sense only as the base of some class derived from it. You can declare the virtual functions of class Shape to be pure virtual functions. A virtual function is "made pure" by the initializer =0.

class Shape { public: virtual void rotate (int) = 0; // pure virtual function virtual void draw () = 0; // pure virtual function virtual bool is_closed () = 0; // pure virtual function // ... };

A class with one or more pure virtual functions is an abstract class, and no objects of that abstract class can be created.

Shape s; // error: variable of abstract class Shape

A n abstract class can be used only as an interface and as a base for other classes.

class Point { /* ... */ }; class Circle : public Shape { Point center; int radius; public: Circle (Point p, int r); public: virtual void rotate (int) {} // override Shape::rotate virtual void draw (); // override Shape::draw virtual bool is_closed () {return true; } // override Shape::is_closed };

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.

An important use of abstract classes is to provide an interface without exposing any implementation details.

Virtual destructors

You cannot use the virtual keyword with constructors, but destructors can and often must be virtual.

The constructor has the special job of putting an object together piece-by-piece, first by calling the base constructor, then the more derived constructors in order of inheritance (it must also call member-object constructors along the way). Similarly, the destructor has a special job: it must disassemble an object that may belong to a hierarchy of classes. To do this, the compiler generates code that calls all the destructors, but in the reverse order that they are called by the constructor. That is, the destructor starts at the most-derived class and works its way down to the base class. This is the safe and desirable thing to do because the current destructor can always know that the base-class members are alive and active. If you need to call a base-class member function inside your destructor, it is safe to do so. Thus, the destructor can perform its own cleanup, then call the next-down destructor, which will perform its own cleanup, etc. Each destructor knows what its class is derived from, but not what is derived from it.

You should keep in mind that constructors and destructors are the only places where this hierarchy of calls must happen (and thus the proper hierarchy is automatically generated by the compiler). In all other functions, only that function will be called (and not base-class versions), whether it's virtual or not. The only way for base-class versions of the same function to be called in ordinary functions (virtual or not) is if you explicitly call that function.

Normally, the action of the destructor is quite adequate. But what happens if you want to manipulate an object through a pointer to its base class? This activity is a major objective in object-oriented programming. The problem occurs when you want to delete a pointer of this type for an object that has been created on the heap with new. If the pointer is to the base class, the compiler can only know to call the base-class version of the destructor during delete. This is the same problem that virtual functions were created to solve for the general case. Fortunately, virtual functions work for destructors as they do for all other functions except constructors.

class Base { public: virtual ~Base () { cout << "~Base()" << endl; } }; class Derived { public: virtual ~Derived () { cout << "~Derived()" << endl; } }; int main() { Base *p = new Derived(); delete p; }

When you run the program, you'll see that delete p calls the derived-class destructor followed by the base-class destructor, which is the behavior we desire. Forgetting to make a destructor virtual is an insidious bug because it often doesn't directly affect the behavior of your program, but it can quietly introduce a memory leak.


References
  1. B.Stroustrup: The C++ programming language. Third edition.
    Section 12: derived classes; pp. 301-325.
  2. Bruce Eckel: Thinking in C++. Second edition.
    Section 15: polymorphism and virtual functions; pp. 627-681.