Previous Section  < Free Open Study >  Next Section

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

}

    Previous Section  < Free Open Study >  Next Section