Microsoft Visual C++ Windows Applications by Example
上QQ阅读APP看书,第一时间看更新

Dynamic Binding

In the example above, it really is no problem to create static objects of the classes. When we call Print on each object, the corresponding version of Print will be called. It becomes a bit more complicated when we introduce a pointer to a Person object and let it point at an object of one of the subclasses. As Print in Person is virtual, dynamic-binding comes into force. This means that the version of Print in the object the pointer actually points at during the execution will be called. Had it not been virtual, Print in Person would always have been called. To access a member of an object given a pointer to the object, we could use the dot notation together with the dereferring operator. However, the situation is so common that an arrow notation equivalent to those operations has been introduced. The following two lines are by definition interchangeable:

pPerson->Print();
(*pPerson).Print();

Main.cpp

#include <iostream>
using namespace std;

#include "Person.h"
#include "Student.h"
#include "Employee.h"
void main()
{
  Person person("John Smith");
  person.Print();
  cout << endl;

  Student student("Mark Jones", "MIT");
  student.Print();
  cout << endl;

  Employee employee("Adam Brown", "Microsoft");
  employee.Print();
  cout << endl;

  Person* pPerson = &person;
  pPerson->Print(); // Calls Print in Person.
  cout << endl;

  pPerson = &student;
  pPerson->Print(); // Calls Print in Student.
  cout << endl;

  pPerson = &employee;
  pPerson->Print(); // Calls Print in Employee.
}

Had we omitted the word virtual in the class Person above, we would not have dynamic-binding, but rather static-binding. In that case, Print in Person would always be called. As mentioned above, static-binding is present for performance reasons only and I suggest that you always mark every method of the baseclass in the class hierarchy as virtual.

Let us take the next logical step and continue with abstract baseclasses and pure virtual methods. An abstract baseclass cannot be instantiated into an object, but can be used as a baseclass in a class hierarchy. In the example above, we became acquainted with virtual methods. In this section, we look into pure virtual methods.

A pure virtual method does not have a definition, just a prototype. A class becomes abstract if it has at least one pure virtual method, which implies that a class cannot be abstract without a pure virtual method. A subclass to an abstract class can choose between defining all pure virtual methods of all its baseclasses, or become abstract itself. In this manner, it is guaranteed that a concrete (not abstract) subclass always has definitions of all its methods.

The next example is a slightly different version of the hierarchy of the previous section. This time, Person is an abstract baseclass because it has the pure virtual method Print. Its prototype is virtual and succeeded with = 0.

The field m_stName is now protected, which means that it is accessible by methods in subclasses, but not by methods of other classes or by freestanding functions. Another difference in these classes is the use of constant references in the constructor. Instead of sending the object itself as an actual parameter, which might be time and memory consuming, we can send a reference to the object. To make sure that the fields of the object are not changed by the method, we mark the reference as constant. Compare the constructor of Person in this case with the previous case. The change does not really affect the program, it is just a way to make the program execute faster and use less memory.

Person.h

class Person
{
  public:
    Person(const string& stName);
    virtual void Print() const = 0;
  protected:
    string m_stName;
};

Person.cpp

#include <string>
using namespace std;

#include "Person.h"

Person::Person(const string& stName)
 :m_stName(stName)
{
  // Empty.
}

As Person is an abstract class, Student must define Print in order not to become abstract itself.

Student.h

class Student : public Person
{
  public:
    Student(const string& stName, const string& stUniversity);
    void Print() const;

  private:
    string m_stUniversity;
};

In the Student class of the previous example, Print called Print in Person. This time, Person does not have a definition of Print, so there is no method to call. Instead, we access the Person field m_stName directly; this is allowed because in this version it is protected in Person.

Student.cpp

#include <string>
#include <iostream>
using namespace std;

#include "Person.h"
#include "Student.h"

Student::Student(const string& stName, const string& stUniversity)
   :Person(stName),
    m_stUniversity(stUniversity)
{
  // Empty.
}

void Student::Print() const
{
  cout << "Name: " << m_stName << endl;
  cout << "University: " << m_stUniversity << endl;
}

Employee works in the same way as Student.

Employee.h

class Employee : public Person
{
  public:
    Employee(const string& stName, const string& stEmployer);
    void Print() const;

  private:
    string m_stEmployer;
};

Employee.cpp

#include <string>
#include <iostream>
using namespace std;

#include "Person.h"
#include "Employee.h"
Employee::Employee(const string& stName,
                   const string& stEmployer)
 :Person(stName),
  m_stEmployer(stEmployer)
{
  // Empty.
}

void Employee::Print() const
{
  cout << "Name: " << m_stName << endl;
  cout << "Company: " << m_stEmployer << endl;
}

In the main function, we cannot create an object of the Person class as it is an abstract class. Neither can we let the pointer pPerson point at such an object. However, we can let it point at an object of the class Student or Employee. The condition for Student and Employee being concrete classes was that they defined every pure virtual method, so we can be sure that there always exists a definition of Print to call.

Main.cpp

#include <string>
#include <iostream>
using namespace std;

#include "Person.h"
#include "Student.h"
#include "Employee.h"

void main()
{
// Does not work as Person is an abstract class.
// Person person("John Smith");
// person.Print();
// cout << endl;

  Student student("Mark Jones", "Berkeley");
  student.Print();
  cout << endl;

  Employee employee("Adam Brown", "Microsoft");
  employee.Print();
  cout << endl;

// In this version, there is no object person to point at.
// Person* pPerson = &person;
// pPerson->Print();
// cout << endl;
  Person* pPerson = &student;
  pPerson->Print(); // Calls Print in Student.
  cout << endl;

  pPerson = &employee;
  pPerson->Print();// Calls Print in Employee.
}