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