Selected C++ Example #15 // Example #15 // This C++ example illustrates the use of virtual multiple // inheritance to force data and constructor sharing of a // common base class. The example comes from the graduate // student problem posed in Chapter 6. #include <iostream.h> #include <string.h> // Constants used in this example. const int name_len = 50; const int ssn_len = 12; const int course_len = 20; const int student_len = 50; // The Person class is the common base class, which both // the student and instructor classes inherit. It would // be inherited twice in the graduate student class if it // were not for virtual inheritance at both the student and // instructor levels. // Note the protected accessor methods. The assumption is // that one or more derived classes of Person need access // to name and social security number. These methods are a // bit dangerous in that they give pointers to the internals // of Person. The fact that they are constant does not help // much, since a user could cast the return value to a // regular char pointer. The only safe way to protect the // state of the Person class would be to force the user to // pass the Person object a buffer. The Person object would // then copy the name/ssn into the required buffer; e.g., // void get_name(char* buf); // void get_ssn(char* buf); // This copying is quite expensive. I would use the above // forms if I were making these public accessors. Since they // are protected, I'm willing to gamble that I do not have // pathological implementors of derived classes. The choice // depends on your level of paranoia. // The copy constructor is not required here, since Person // is a fixed-size class. I place it here for readability // since it will be called further down the hierarchy. class Person { char name[name_len]; char ssn[ssn_len]; protected: const char* get_name() { return (name); } const char* get_ssn() { return(ssn); } Person(); public: Person(char*, char*); Person(Person&); void print(); }; // The first uncomfortable item we need to deal with is // the requirement that the Person constructor possess this // constructor, which, in fact, will never be called. We // want to be able to initialize a graduate student given // only a student object and a salary. This requires us to // have a protected constructor for the Instructor class, // which takes a salary (only) as an argument. Such a // constructor will have an implied call to this constructor // (see the protected instructor constructor below) , but // since the Instructor virtually inherits from Person, this // constructor will never be called. Without it, however, // the example will not compile. // We also want to build a graduate student from only an // Instructor. This requires a protected student // constructor that takes no arguments. The same problem that // occurs with the instructor constructor occurs here. Person::Person() { } Person::Person(char* n, char* social_num) { strncpy(name, n, name_len); strncpy(ssn, social_num, ssn_len); } Person::Person(Person& rhs) { strcpy(name, rhs.name); strcpy(ssn, rhs.ssn); } void Person::print() { cout << ''Hi! My name and SSN is '' << name; cout << '' '' << ssn << ''.\n''; } // The Student class contains a list of courses, each of // which contains a name and a grade. class Course { char name[name_len]; int grade; public: Course(char*, int); Course(Course&); void print(const char*); }; Course::Course(char* n, int g) { strncpy(name, n, name_len); grade = g; } Course::Course(Course& rhs) { strcpy(name, rhs.name); grade = rhs.grade; } void Course::print(const char* student) { cout << student << '' received a '' << grade; cout << '' in the course '' << name << ''\n''; } // The Student class virtually inherits from Person. The // Student class is advertising that it is willing to share // its Person base object with any other Person base object // in a multiple inheriting derived class (the GradStudent, in // this case). This virtual keyword has nothing to do with // polymorphism. In fact, there is no polymorphism in this // example. The behavior of Student is defined to be the same // regardless of the virtual keyword; its implementation // changes, however. Virtual inheritance will affect only the // children of the virtually inheriting class. class Student : virtual public Person { Course* course_list[course_len]; double GPA; int grade_sum; int course_num; protected: Student(); public: Student(char*, char*); Student(Student&); ~Student(); int add_course(char*, int); void print(); }; // This protected constructor is called only indirectly from // the graduate student constructor. The implied call to a // Person constructor, which is callable with zero arguments, // necessitates the protected Person constructor above. But, // since this constructor is never called directly, that // person constructor will never be executed. The result is // a required constructor that can never be called. Student::Student() { int i; GPA = 0.0; grade_sum = course_num = 0; for (i=0; i < course_len; i++) { course_list[i]=NULL; } } // If this constructor is called directly, i.e., someone is // building a Student, then it will call the Person // constructor. If it is called indirectly from a GradStudent // constructor, then the Person constructor will not be called. // The GradStudent constructor will be responsible for the // call to the Person constructor. Student::Student(char* name, char* ssn) : Person(name, ssn) { int i; GPA = 0.0; grade_sum = course_num = 0; for (i=0; i < course_len; i++) { course_list[i] = NULL; } } Student::Student(Student& rhs) : Person(rhs) { int i; GPA = rhs.GPA; grade_sum = rhs.grade_sum; course_num = rhs.course_num; for (i=0; i < course_num; i++) { course_list[i] = new Course(*rhs.course_list[i]); } } Student::~Student() { int i; for (i=0; i < course_num; i++) { delete course_list[i]; } } int Student::add_course(char* name, int grade) { course_list[course_num++] = new Course(name, grade); grade_sum += grade; GPA = grade_sum / course_num; return(course_num); } void Student::print() { int i; cout << ''Student Name: '' << get_name() << ''\n''; cout << ''Social Security Number: '' << get_ssn() << ''\n''; cout << ''Courses: \n''; for (i=0; i < course_num; i++) { cout << ''\t''; course_list[i]->print(get_name()); } if (course_num) { cout << ''Grade Point Average: '' << GPA << ''\n''; } cout << ''\n\n''; } // The Instructor class must also virtually inherit if the // Person object is to be shared at the GradStudent level. // All base classes wishing to share a common base class // in a multiple inheriting derived class must virtually // inherit. class Instructor : virtual public Person { double salary; Student* students[student_len]; int student_num; protected: Instructor(double); public: Instructor(char*, char*, double); Instructor(Instructors); ~Instructor(); int add_student(Student&); void print(); }; // This protected constructor has an implied call to a Person // constructor callable with zero arguments. This required // us to define such a constructor above. But since this // constructor is protected, it will only be called by derived // constructors. When called indirectly, this constructor will // NOT call Person's constructor. The result is a needed // constructor for compiling, which is never really called. Instructor::Instructor(double sal) { int i; salary = sal; student_num = 0; for (i=0; i < student_len; i++) { students[i] = NULL; } } Instructor::Instructor(char* name, char* ssn, double pay) : Person(name, ssn) { int i; student_num = 0; salary = pay; for (i=0; i < student_len; i++) { students[i] = NULL; } } Instructor::Instructor(Instructor& rhs) : Person(rhs) { int i; salary = rhs.salary; student_num = rhs.student_num; for (i=0; i < rhs.student_num; i++) { students[i] = new Student(*rhs.students[i]); } } Instructor::~Instructor() { int i; for (i=0; i < student_num; i++) { delete students[i]; } } int Instructor::add_student(Student&new_student) { students[student_num++] = new Student(new_student); return(student_num); } void Instructor::print() { int i; cout << ''Instructor Name: '' << get_name() << ''\n''; cout << ''Salary: '' << salary << ''\n''; if (student_num) { cout << ''Cost per Student: '' << salary/student_num << ''\n''; } cout << ''Students: \n''; for (i=0; i < student_num; i++) { students[i]->print(); } cout << ''\n\n''; } // The Grad_student class multiple inherits from Instructor // and Student. Since they both virtually inherit from // the Person class, they will share the same Person object. // Also, their constructors will not call the Person // constructor. The Grad_student is responsible for that // initialization, as we will see below. // The Grad_student class has three constructors: one that // builds a graduate student from a name, social security // number, and salary; one that builds a graduate student // from a student object and a salary; and a third that // builds a graduate student from an instructor. class Grad_student : public Instructor, public Student { public: Grad_student(char*, char*, double); Grad_student(Student&,double); Grad_student(Instructor&); void print(); }; // This constructor requires three additional constructor // calls. The first constructor to be called will be // the Person constructor, which takes a name and social // security number. (Because all virtually inheriting base // classes are called first.) The second constructor will // be the Instructor constructor because it was the first // class to be inherited in the class definition above. (Note: // The order that constructor calls appear in the constructor // definition is irrelevant. The importance is the order of // the class definition.) Lastly, a call to the protected // Student constructor callable with zero arguments is made. // It is important to note that neither the Student or // Instructor constructors will call their Person constructor. // They would have made these calls if they were called // directly. Grad_student::Grad_student(char* name, char* ssn, double salary) : Instructor(salary), Person(name, ssn) { } // This constructor calls Person' s copy constructor, followed // by Instructor's constructor, which takes a salary, followed // by Student's copy constructor. Grad_student::Grad_student(Student& rhs, double salary) : Student(rhs),Instructor(salary),Person(rhs) { } // This constructor calls Person's copy constructor, followed // by Instructor's copy constructor, followed by Student's // protected constructor callable with zero arguments. Grad_student::Grad_student(Instructor&rhs) : Instructor(rhs), Person(rhs) { } // The graduate student must resolve ambiguity on the print // method between Student and Instructor. In this case it // chooses a boring solution. It could have been more // elaborate by calling each base class method. It is useful // to note that there is no ambiguity on the call to get_name() // even though it can be inherited via student or instructor. // The compiler recognizes that both paths give it the same // function. void Grad_student::print() { cout << ''I'm just a grad student named: ''; cout << get_name() << ''\n''; // Could have printed both like: // Student::print(); // Instructor::print(); } void main() { Student x(''Arthur J. Riel'', ''038-48-9922''); Student y(''John Doe'', ''234-78-9988''); x.print(); x.add_course(''Biology 101'', 94); x.add_course(''Physics 307'', 35); x.add_course(''Computer Science 101'', 98); x.add_course(''Advanced C++", 78); x.print(); y.add_course(''Biology 207'', 87); y.add_course(''Organic Chemistry'', 67); y.add_course(''English 109'', 100); Student z = x; z.add_course(''Introduction to Latin'', 89) ; z.add_course(''Running for Fun'', 84); z.add_course(''Basket Weaving 101'', 100); Instructor loco(''Chris Roth'', ''934-76-4365'', 29400); loco.add_student(x); loco.add_student(y); loco.add_student(z); loco.print(); // Build a graduate student from a student. Grad_student g1(x, 14800); // Build a graduate student from scratch. Grad_student g2(''Bob Miller'', ''888-44-7765'', 34900L); // Build a graduate student from an instructor. Grad_student g3(loco); g3.add_course(''Post-Doc 101", 82); g3.add_student(x); cout << ''\n\nPrinting Grad Student g1 as a student\n''; ((Student*) &g1)->print(); cout << ''Printing Grad Student g1 as a Instructor\n''; ((Instructor*) &g1)->print(); cout << ''Printing Grad Student g1 as a grad student\n''; g1.print(); cout << ''\n\nPrinting Grad Student g2 as a student\n''; ((Student*) &g2)->print(); cout << ''Printing Grad Student g2 as a Instructor\n''; ((Instructor*) &g2)->print(); cout << ''Printing Grad Student g2 as a grad student\n''; g2.print(); cout << ''\n\nPrinting Grad Student g3 as a student\n''; ((Student*) &g3)->print(); cout << ''Printing Grad Student g3 as a Instructor\n''; ((Instructor*) &g3)->print(); cout << ''Printing Grad Student g3 as a grad student\n''; g3.print(); } |