6.2. Derivation with Polymorphism

[ fromfile: inheritance-intro.xml id: polymorphism ]

We now introduce another powerful feature of object-oriented programming: polymorphism. Example 6.6 differs from the previous example in only one important way: the use of the keyword virtual in the base class definition.

Example 6.6. src/derivation/qpoly/student.h

[ . . . . ]

class Student  {
 public:
    Student(QString nm, long id, QString major, int year = 1);
    virtual ~Student() {}                1
    virtual QString getClassName() const;2
    QString toString() const;            3
 private:
    QString m_Name;
    QString m_Major;
    long m_StudentId;
 protected:
    int m_Year;
    QString yearStr() const;
};

1

We added the keyword virtual here.

2

We added virtual here also.

3

This should also be virtual.

<include href="src/derivation/qpoly/student.h" segid="student" src="src/derivation/qpoly/student.h" mode="cpp" id="src-polystu"/>


Adding the keyword virtual to at least one member function creates a polymorphic type. virtual functions are called methods. This terminology is consistent with the use of that term in Java, where member functions are "methods" by default. Example 6.7 shows the same client code again.

Example 6.7. src/derivation/qpoly/student-test.cpp

#include <QTextStream> 
#include "student.h"

static QTextStream cout(stdout); 

void finish(Student* student) {
    cout << "\nThe following "
         << student->getClassName()
         << " has applied for graduation.\n "
         << student->toString() << "\n";
}

int main() {
    Undergrad us("Frodo Baggins", 5562, "Ring Theory", 4, 1220);
    GradStudent gs("Bilbo Baggins", 3029, "History", 6, 
     GradStudent::fellowship);
    cout << "Here is the data for the two students:\n";
    cout << gs.toString() << endl;
    cout << us.toString() << endl;
    cout << "\nHere is what happens when they finish their studies:\n";
    finish(&us);
    finish(&gs);
    return 0;
}

<include src="src/derivation/qpoly/student-test.cpp" href="src/derivation/qpoly/student-test.cpp" id="src-polyclient" mode="cpp"/>


When you run this slightly changed program you get the following output.

Here is the data for the two students:
[GradStudent] name: Bilbo Baggins; Id: 3029; Year: gradual student;
  Major: History  [Support: fellowship ]

[Undergrad] name: Frodo Baggins; Id: 5562; Year: senior; 
  Major: Ring Theory  [SAT: 1220 ]


Here is what happens when they finish their studies:

The following Undergrad has applied for graduation.
 [Undergrad] name: Frodo Baggins; Id: 5562; Year: senior;
   Major: Ring Theory [46]

The following GradStudent has applied for graduation.
 [GradStudent] name: Bilbo Baggins; Id: 3029; Year: gradual student;
   Major: History[47]
  

[GradStudent] and [UnderGrad] now appear in the output, because getClassName() is virtual. There is still a problem with the output of finish() for the GradStudent, however. The Support piece is missing.

With polymorphism, indirect calls (via pointers and references) to methods are resolved at runtime. This is called dynamic, or runtime binding. Direct calls (not through pointers or references) of methods are resolved by the compiler. That is called static binding or compile time binding.

In this example, when finish() receives the address of a GradStudent object, student->toString() calls the Student version of the function. However, when the Student::toString() calls getClassName() (indirectly through this, a base class pointer), it is a virtual method call, bound at runtime.

In C++, dynamic binding is an option that one must switch on with the keyword virtual.

[Note] Virtual calls from Constructors

Because "this" is in the process of being initialized while its constructor is executing (or destroyed while its destructor is executing), it is not reasonable to expect runtime binding to work properly under those conditions. In particular, because the virtual table (essential for runtime binding) may be incompletely set up by the constructor (or may be partially or completely destroyed by the destructor), compile-time binding will determine which method is called – as if the virtual keyword was not even there – when invoking any method of "this" from inside a constructor or destructor. As Scott Meyers [Meyers] likes to say: "from a constructor or destructor, virtual methods aren't." See Section 22.1 for an example.

[Note] Virtual Destructors

In general, if a class has one or more virtual functions, it should also have a virtual destructor. This is because when operating on a collection of polymorphic objects, it is common to delete objects through base class pointers, which results in an indirect call to the destructor. If the destructor is not virtual, compile-time binding determines which destructor is called and may result in incomplete destruction of the derived object.



[46] Missing: SAT score.

[47] Missing: Fellowship.