Termbank
  1. A
    1. API
    2. ASAN
    3. Access specifiers
  2. C
    1. Constructor
    2. const
  3. G
    1. GDB
    2. g++
  4. M
    1. Make
    2. Memory Leak
  5. P
    1. Pointer
  6. R
    1. Recursion
  7. S
    1. std
  8. V
    1. Valgrind
Completed: / exercises

LAB_INHERITANCE: Virtual Functions and Abstract Classes

Due date: 2024-04-08 23:59.

Lab Description

In this lab you will get experience with some of the implementation issues and conceptual details of inheritance. Inheritance is a mechanism for increasing the reusability and reliability of C++ code. It is worth mentioning that inheritance is a characteristic of all object oriented programming languages. Our goal is to give you a glimpse of the functionality of inheritance, so that you can make informed design decisions in the future. Please read through the entire lab before you begin. The compilation notes at the bottom will tell you how to organize your development files.

Download The Provided Files

Just like you already did it in previous labs, start with downloading and unzipping the provided files from here: lab_inheritance.zip.
There will be many files in your lab_inheritance directory, but you will only need to modify the following files:

Background on Class Inheritance

To help us understand class hierarchies better here is an example of a simple class hierarchy showing that a Dog is an Animal.
Dog is Animal image
The code would look something like the following:
class Animal {
   public:
      string name;

      virtual void speak() = 0;
      /* The = 0 at the end of the method means that the method is a pure virtual method
       * meaning that it does not have an implementation and it delegates the task
       * of implementing the method to the classes that is derived from it */
};

class Dog : public Animal {
   public:
      string breed;

      /* Dog inherits speak from Animal */
      void speak();

};

void Dog::speak() {
    cout << "Woof Woof" << endl;
}
In this example Animals have a name and can speak but since speak is a pure virtual method we CANNOT construct an Animal by itself. That is Animal is an abstract class and it can only be constructed by one of its derived classes. For example, a Dog is a derived class of Animal. This means that a Dog is an Animal, and, therefore, it inherits a name and a speak method from Animal. However, since the Animal's speak does not have an implementation, Dog MUST implement the speak method.
Here is an example of how we could use a Dog object:
Dog* d = new Dog();

/* Like usual we can access all the public methods and member variables of a
 * Dog */
d->breed;

/* But now since a Dog is an Animal we can also do this too */
d->name;     // inherited from Animal
d->speak();  // inherited from Animal and since it is a Dog speak() will print
             // "Woof Woof"

/* Additionally we can treat our Dog only like an Animal like this */
Animal* a = d;

/* But now we can only do the following */
a->name;
a->speak();  // Still prints "Woof Woof" because speak is a virtual method.

a->breed     // ERROR! This will NOT work since we perceive it as an Animal now

/* Additionally, if we try to have our Animal pointer point back to a Dog
 * pointer this will cause a problem because an Animal Is NOT A Dog. */
Dog* d2 = a;  // ERROR!  Animal Is NOT A Dog

/* Furthermore, since Animal is abstract and has a pure virtual method
 * we CANNOT construct one! */
Animal a2;  // ERROR! Animal is an abstract class

Class Hierarchy

Now that we can understand a simple class hierarchy, let's look at a more complex one. Here is a diagram depicting the class hierarchy that is used in this lab. (Note: This diagram is missing some information, e.g. methods, member variables, etc.., for demonstration purposes)
Complex Class Hierarchy
This means everything is a Drawable and will have a draw method. Code like the following is perfectly acceptable:
Drawable* triangle  = new Triangle(....);
Drawable* circle    = new Circle(...);
Drawable* rectangle = new Rectangle(....);
Drawable* truck     = new Truck(...);
Drawable* flower    = new Flower(....);

/* Now the only thing we can use on triangle, circle, rectangle, truck, and
 * flower is draw but what gets drawn will change depending on what type the
 * pointer is actually pointing to. This is called polymorphism, the behavior
 * changes depending on the actual type of the object being pointed to. */

PNG canvas;
triangle->draw(&canvas);   // draws a Triangle even though triangle is a Drawable*
circle->draw(&canvas);     // draws a Circle even though circle is a Drawable*
rectangle->draw(&canvas);  // draws a Rectangle even though rectangle is a Drawable*
truck->draw(&canvas);      // draws a Truck even though truck is a Drawable*
flower->draw(&canvas);     // draws a Flower even though flower is a Drawable*

Fully Working Code in main.cpp

Look at main.cpp for a working example executable. main.cpp gets compiled and linked into an executable named lab_inheritance. Follow the instructions below to build, run, and view the output. Note that unlike most labs, this main executable is not what will be tested, but rather it is an example for you to look at to help understand how to fix the other parts.
The Makefile provided for this lab will create several useful executables when you run make. Two of them are lab_inheritance and lab_inheritance-asan. There are other executables that correspond to the broken parts of the code, but you can also compile them separately later. So when you want to test a specific executable, you can run either the executable itself, or the -asan version of it to detect any memory errors. For example, you could run:
./lab_inheritance
You could also run it as:
./lab_inheritance-asan 
Additionally, you're free to run Valgrind on the normal executable:
valgrind --leak-check=full ./lab_inheritance 
The output of running ./lab_inheritance is out.png. You can see that the code in main.cpp has no bugs, as the output matches the provided soln_out.png file. However, this is not the case for other executables in this lab, you will quickly see that they are not working the way they should. Your objective for this lab is to go through the five test_* executables and fix the code to work correctly by modifying how the classes in the hierarchy declare and implement their methods.

A Note on the Output .png Files

For all of the exercises, the executable will produce a .png file that you can compare to the correct output. Look for .png files starting with soln_*, those are the correct solution outputs. Then, for each of the following exercises, compare your output .png to the correct solution soln_*.png while also making sure the console output is correct and there are no memory leaks.

Exercise 1: Fix the Virtual Methods

Start with building and running test_virtual:
make test_virtual # make test_virtual
./test_virtual-asan # run test_virtual with asan
valgrind ./test_virtual # run test_virtual with valgrind
As you will see when you run test_virtual, the output will say:
The Perimeters are NOT the same.
The Areas are NOT the same.
However, if you look closely at the code they should be the same because both of the pointers in test_virtual.cpp point to the same object!
Checklist:

Submit Your Solution

Upload your shape.cpp and shape.h here. Make sure the area and perimeter match and that there are no memory leaks.

Allowed filenames: shape.h, shape.cpp

Warning: You have not logged in. You cannot answer.

Exercise 2: Fix the Destructor

Please build and run test_destructor:
make test_destructor 				# make test_destructor
valgrind --leak-check=full ./test_destructor 	# run test_destructor in valgrind
make test_destructor-asan			# make test_destructor-asan
./test_destructor-asan                          # test it with Address Sanitizer
When you run test_destructor in Valgrind or ASAN you will see that test_destructor is leaking memory. However, if you look closely, Triangle does have a valid destructor and it is being called in test_destructor!
Checklist:

Fix the Destructor

Make sure test_destructor compiles and runs without memory leaks.

Allowed filenames: drawable.h, shape.h

Warning: You have not logged in. You cannot answer.

Exercise 3: Fix the Constructor

Please build and run test_constructor:
make test_constructor # make test_constructor
./test_constructor # run test_constructor
When you run test_constructor you will see the following output:
Circle's color is NOT correct!
Circle's center is NOT correct!
If you look closely, we are constructing a Circle with a valid center and color. However, when it is being drawn and when we ask for the Circle's center and color they are not the same!
Checklist:
Complex Class Hierarchy

Fix the Constructor

Ensure that the terminal output is correct and that the produced test_constructor.png looks correct.

Allowed filenames: circle.cpp

Warning: You have not logged in. You cannot answer.

Exercise 4: Fix the Pure Virtual Method

Please build and run test_pure_virtual.
make test_pure_virtual # make test_pure_virtual
./test_pure_virtual # run test_pure_virtual
When you try to make test_pure_virtual you will see that it does not compile.
However, if you look at the truck.{cpp,h}, it is a fully featured class! Why is it not compiling?
Checklist:
Complex Class Hierarchy

Fix the Pure Virtual Method

Ensure that test_pure_virtual compiles and runs without issues, and that the output image test_pure_virtual.png matches the solution exactly. Pay attention to the order in which the different elements are drawn such that overlapping shapes are drawn as intended.

Allowed filenames: truck.h, truck.cpp

Warning: You have not logged in. You cannot answer.

Exercise 5: Fix the Slicing

Please build and run test_slicing with:
make test_slicing    # make test_slicing
./test_slicing       # run test_slicing
After you run test_slicing open up its output test_slicing.png. You will see that a Flower has NOT been drawn. For some reason just a bunch of X's has been drawn and a Red Circle.
If you look at flower.h and flower.cpp, we have all of the proper member variables set up. However, when we try to draw them they are drawn incorrectly.
Checklist:
Complex Class Hierarchy

Fix the Slicing

Make sure that test_slicing compiles and runs without issues and that the produced test_slicing.png looks correct!

Allowed filenames: flower.h, flower.cpp

Warning: You have not logged in. You cannot answer.

Acknowledgments

We would like to express our gratitude to prof. Cinda Heeren and the student staff of the UIUC CS Data Structures course for creating and sharing the programming exercise materials that have served as the basis for the labs used in this course.
Edited by: Elmeri Uotila and Anna LaValle
?
API stands for Application Programming Interface. In the context of this course, you can consider the header files to be such interfaces, as they determine what class functions and properties are public and thus accessible from anywhere.
AddressSanitizer (ASAN) is a memory error detector for C/C++. In this course, the makefiles will typically compile an executable that uses ASAN, with "-asan" at the end of its name.
The two notable access specifiers are:
  • public: class members defined after the public keyword are accessible from outside the class.
  • private: class members are generally private by default and thus not accessible from the outside
Constructor is a special non-static member function of a class that is used to initialize objects of its class type. A constructor is called upon initialization of an object. A constructor without arguments is a default constructor, but constructors that take arguments can be defined.
GDB, the GNU Project debugger, allows you to see what is going on `inside' another program while it executes -- or what another program was doing at the moment it crashed.
GDB can do four main kinds of things (plus other things in support of these) to help you catch bugs in the act:
  • Start your program, specifying anything that might affect its behavior.
  • Make your program stop on specified conditions.
  • Examine what has happened, when your program has stopped.
  • Change things in your program, so you can experiment with correcting the effects of one bug and go on to learn about another.
GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program's source files. Make gets its knowledge of how to build your program from a file called the makefile, which lists each of the non-source files and how to compute it from other files. When you write a program, you should write a makefile for it, so that it is possible to use Make to build and install the program.
Memory leak means that the program is not freeing up memory it reserves. The memory will be freed when the program terminates, but if a program keeps leaking more and more memory without terminating, it can become a huge issue!
A typical way for memory leaks to occur is reserving memory with new and not calling delete before the pointer goes out of scope.
Pointer variables store a memory address as their value. In other words, they point to some data. The data can be accessed by dereferencing the pointer. (Either like *p or p->...)
A recursive function calls itself from within it. The recursion call must be conditional or it would lead to an infinite loop.
Valgrind is another tool besides ASAN that you can use in this course. It can detect many memory-related errors that are common in C and C++ programs and that can lead to crashes and unpredictable behaviour.
const is a keyword meant to mark something as immutable
  • A const object cannot be modified: attempt to do so directly is a compile-time error, and attempt to do so indirectly (e.g., by modifying the const object through a reference or pointer to non-const type) results in undefined behavior.
  • const keyword on an object's member function prevents the object from being modified in the function
  • Pointer declarations can have 2 const keywords, one to make the data that's pointed at unable to be modified and one to make the pointer itself unable to be modified
Using const improves code readability and prevents accidental modification of objects.
g++ is a C++ compiler that we primarily use for this course. It is the C++ compiler for the GNU compiler collection. You may sometimes see gcc being used instead of g++, which was originally the GNU C compiler, but calling gcc generally also compiles .cpp files as C++. However, calling g++ is preferred.
In C++, std stands for Standard Library, which is a collection of commonly useful classes and functions. Typically, these are defined in the std namespace.