This assignment consists of two parts. The first part is easy. The second part is hard, but interesting and educational. Having done the first part, you will have a superficial knowledge of how to use inheritance and polymorphism in C++. Having done the second part, you will understand how polymorphism may actually work under the hood and what cost we pay for it.
The first part of the assignment is focused on the basics of inheritance and polymorphism. For this part, all things are happening inside file dynb-virt.cpp. This file contains declarations and definitions of two classes — Student and CollegeStudent —, as well as a small program that tests them. (Usually, we separate class declaration and definition into two files, but in this case, we will keep everything together.)
Class Student represents some abstract student, who has a name and a date of birth. Class CollegeStudent, representing a college student, extends class Student with a student's perm-number. Both classes have two constructors and a few methods for printing some information.
Class Student contains method void print(). This method is what programmers usually call a template method — it is a software architectural pattern, and has nothing to do with C++ Templates. A template method is (often, a non-virtual) method defined in a base class, and the body of such a method contains multiple virtual calls — calls of virtual methods of the same class. The name "template method" stems from the fact that such a method contains some "static" code which is the same regardless what, as well as some "dynamic" code — virtual calls that may reach different implementations of virtual functions, depending on the type of the current object. Thus, a template method indeed defines a template of behavior.
Method void print() should not change in derived classes; it should be inherited as is. However, the virtual methods method print calls can (and should) change in derived classes. In particular, there are two should-be-virtual methods, print_classname and print_state. In the code you are provided with, these methods are initially not marked as virtual — you will need to change that. Method print_classname just prints the name of the current class. For example, if called on an object of type CollegeStudent, it should print "CollegeStudent" (without quotes). Method print_state prints out the state of the current object, i.e., the values of all the fields that the current object contains. Clearly, objects of different classes have different sets of fields, so method print_state should work differently for objects of different classes (but its implementations should be related, in that its derived implementation should use its base implementations).
First, you need to compile code with methods print_classname and print_state as they are (non-virtual) and run dynb-virt. Then, you need to make these two methods virtual and compile and run the program again. You should understand why these two executions produce different results (though, you do not need to report on that in any form).
Second, having understood the effect of making print_classname and print_state virtual, you will need to derive a new class CollegeGraduate from class CollegeStudent. This new class extends the state of its base class with one boolean field _honors, which indicates whether a particular college graduate has graduated with honors. The new class should be similar to its base class CollegeStudent — it should have two ctors (one default and one parameterized) and its own versions of methods print_classname and print_state.
To test your new class, you can uncomment the bottom section of the test program in the same file. If you have already changed methods print_classname and print_state in the base class to be virtual and uncommented the bottom section of main(), you should get the following output when running dynb-virt:
Student { name = John Doe, dob = 12311990 } ------------------------- CollegeStudent { name = Jane Doe, dob = 11211989, perm = UC-12345 } ------------------------- CollegeStudent { name = Jane Doe, dob = 11211989, perm = UC-12345 } ------------------------- CollegeGraduate { name = Bilbo Baggins, dob = 2128506, perm = MiddleEarthUniversity-#24123, honors = yes } -------------------------
Please notice that
make dynb-virt
./dynb-virt
When a particular implementation of a method is called based on the type of the variable used in the declaration, then such a call is performed through static dispatch. In other words, it is a non-virtual call, and which implementation should be executed is decided at compile time (when your code is being compiled). Virtual calls are performed through dynamic dispatch, that is, a particular implementation of a virtual method is chosen at run-time (when your program is actually running) depending on the type of the object the pointer or reference points to.
In C++, dynamic dispatch is provided in the form of virtual functions. In this part of the assignment, you will implement a simple version of dynamic dispatch mechanism. In particular, you will need to implement the same functionality as you have done in the first part of this assignment, but, now, without using virtual functions. Your implementation of dynamic dispatch will be somewhat different from what most contemporary C++ compilers do, but it should be good enough to understand how virtual functions work.
For this part of the assignment, things happen in three files — dynb.h, dynb.cpp, and dynb-main.cpp. The first two files contain class declarations and definitions, respectively, and the third file contains a small test program. You will work mainly with the first two files, and, later, uncomment some code in the last file to test your code.
dynb.h and dynb.cpp contain declarations and definitions of two classes — Student and CollegeStudent. These classes are similar to those from the first part of the assignment, except that methods print_classname and print_state and not marked as virtual, and our intention will be to make these methods "virtual" without using the C++ virtual keyword.
Class Student, like in the first part, is the base class, with one extra field _pvtbl. This field will be inherited by all the derived classes and will point to a particular virtual table. A virtual table is an object of type vtbl_t (declared and defined in the same files dynb.(cpp|h)) that contains pointers to particular implementations of members print_classname and print_state (these are just regular C pointers to functions aliased with a typedef printer_t). When method print() needs to make a "virtual call" to one of these two methods, instead of calling the appropriate method directly, it will look in the virtual table, pick the pointer to the appropriate method and call this method through the pointer. By assigning different virtual tables to objects, we will achieve what C++ achieves by the means of virtual functions.
Notice that virtual tables are chained, that is, each virtual table contains a pointer to a virtual table of the base class of the class the virtual table belongs to. This is done to allow "virtual" functions to call their base implementations (though, to make it work, we need some inelegant macro definitions you may notice in dynb.cpp).
The framework for dynamic dispatch as well as two classes, Student and CollegeStudent, using this framework are already implemented. First, you need to understand how these two classes work by compiling and running the test program dynb-main.cpp.
Second, you will need to derive new class CollegeGraduate from CollegeStudent. Like in the first part of the assignment, CollegeGraduate will extend the state of CollegeStudent with one boolean field _honors. Additionally, this class will have two constructors and methods print_classname and print_state. This time, you cannot mark these two methods virtual.
You will need to create a vtbl_t for your new class CollegeGraduate and use it inside CollegeGraduate where appropriate (see how it is done in CollegeStudent). The declaration of CollegeGraduate should be places in dynb.h and its definition should be placed in dynb.cpp. The virtual table for CollegeGraduate should be both declared and defined in dynb.cpp. When done, do not forget to uncomment the bottom part of dynb-main.cpp to test your new class.
Like in the first part of the assignment, you need to keep fields private. The only non-private field should be _pvtbl declared in the base class. Also, you are not allowed to change method print() of class Student. You cannot use C++ virtual functions in this part of the assignment.
make dynb
./dynb
If CollegeGraduate is implemented correctly, the output of dynb should be exactly like the output of dynb-virt:
Student { name = John Doe, dob = 12311990 } ------------------------- CollegeStudent { name = Jane Doe, dob = 11211989, perm = UC-12345 } ------------------------- CollegeStudent { name = Jane Doe, dob = 11211989, perm = UC-12345 } ------------------------- CollegeGraduate { name = Bilbo Baggins, dob = 2128506, perm = MiddleEarthUniversity-#24123, honors = yes } -------------------------(You are provided a script diff.sh that checks whether your dynb-virt and dynb produce the same output. It expects that you have run make and, thereby, produced both executables dynb and dynb-virt prior to running the script.)
Submit dynb-virt.cpp, dynb.h, and dynb.cpp. Do not forget to write your names in the header of each file you submit. As usually, log in to csil.cs.ucsb.edu and run
turnin pa4@cs32 ./dynb-virt.cpp ./dynb.h ./dynb.cppand follow instructions. You can submit your work multiple times. Submissions are not accepted after the deadline.