6.2. Derivation with Polymorphism

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

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.


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;
}

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 [28]

The following GradStudent has applied for graduation.
 [GradStudent] name: Bilbo Baggins; Id: 3029; Year: gradual student;
   Major: History[29]
  
[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.



[28] Missing: SAT score.

[29] Missing: Fellowship.