18.x — Chapter 18 comprehensive quiz

And so our journey through C++’s inheritance and virtual functions comes to an end. Fret not, dear reader, for there are plenty of other areas of C++ to explore as we move forward.

Chapter summary

C++ allows you to set base class pointers and references to a derived object. This is useful when we want to write a function or array that can work with any type of object derived from a base class.

Without virtual functions, base class pointers and references to a derived class will only have access to base class member variables and versions of functions.

A virtual function is a special type of function that resolves to the most-derived version of the function (called an override) that exists between the base and derived class. To be considered an override, the derived class function must have the same signature and return type as the virtual base class function. The one exception is for covariant return types, which allow an override to return a pointer or reference to a derived class if the base class function returns a pointer or reference to the base class.

A function that is intended to be an override should use the override specifier to ensure that it is actually an override.

The final specifier can be used to prevent overrides of a function or class.

If you intend to use virtual functions, you should make your destructor virtual, so the proper destructor is called if a pointer to the base class is deleted.

You can ignore virtual resolution by using the scope resolution operator to directly specify which classes version of the function you want: e.g. base.Base::getName()

Early binding occurs when the compiler encounters a direct function call. The compiler or linker can resolve these function calls directly. Late binding occurs when a function pointer is called. In these cases, which function will be called can not be resolved until runtime. Virtual functions use late binding and a virtual table to determine which version of the function to call.

Using virtual functions has a cost: virtual functions take longer to call, and the necessity of the virtual table increases the size of every object containing a virtual function by one pointer.

A virtual function can be made pure virtual/abstract by adding “= 0” to the end of the virtual function prototype. A class containing a pure virtual function is called an abstract class, and can not be instantiated. A class that inherits pure virtual functions must concretely define them or it will also be considered abstract. Pure virtual functions can have a body, but they are still considered abstract.

An interface class is one with no member variables and all pure virtual functions. These are often named starting with a capital I.

A virtual base class is a base class that is only included once, no matter how many times it is inherited by an object.

When a derived class is assigned to a base class object, the base class only receives a copy of the base portion of the derived class. This is called object slicing.

Dynamic casting can be used to convert a pointer to a base class object into a pointer to a derived class object. This is called downcasting. A failed conversion will return a null pointer.

The easiest way to overload operator<< for inherited classes is to write an overloaded operator<< for the most-base class, and then call a virtual member function to do the printing.

Quiz time

1) Each of the following programs has some kind of defect. Inspect each program (visually, not by compiling) and determine what is wrong with the program. The output of each program is supposed to be “Derived”.


Show Solution


Show Solution


Show Solution


Show Solution


Show Solution


Show Solution

2a) Create an abstract class named Shape. This class should have three functions: a pure virtual print function that takes and returns a std::ostream, an overloaded operator<< and an empty virtual destructor.

Show Solution

2b) Derive two classes from Shape: a Triangle, and a Circle. The Triangle should have 3 Points as members. The Circle should have one center Point, and an integer radius. Overload the print() function so the following program runs:

This should print:

Circle(Point(1, 2, 3), radius 7)
Triangle(Point(1, 2, 3), Point(4, 5, 6), Point(7, 8, 9))

Here’s a Point class you can use:

Show Solution

2c) Given the above classes (Point, Shape, Circle, and Triangle), finish the following program:

Hint: You’ll need to add a getRadius() function to Circle, and downcast a Shape* into a Circle* to access it.

Show Solution

19.1 -- Template classes
18.11 -- Printing inherited classes using operator<<

171 comments to 18.x — Chapter 18 comprehensive quiz

  • Christofle

    Hope I can get a feedback!
    Thanks so much for the chapter and quiz.

    • David

      Hey, this looks pretty ok to me. Just a few things i would mention:

      Line 26: no "protected" access specifier needed.
      Line 41: You should stick to one design pattern, at line 13 you used the member-list initializer list in the next row, this time you use it in the same one. I'd             recommend using it in the next line, this can help with preventing lines to get too long. (This is completely optional)

      Line 45/57: you dont need to make the most derived classes a virtual defaulted constructor (wont harm you in both cases)
      Line 65: You should avoid using std::size_t in for loops! they are unsigned, so if you would subtract from 0, it would wrap around and produce a really high
      number (on my machine: 4294967295).
      Line 65: Even tho a for loop is wrong here, you could make your program simpler by using a ranged for loop (for-each loop).

      In general, it is pretty good code but personally i find it a bit hard to read due to the fact that you did not really make use of spaces inside of your class, but again, this is just a personal preference.

  • michael oska

    hello Alex and Nascardriver

    again thank you for the great tutorial.

    in the last example in the Point class, why we gave default value to

    if we can t use it anyway? because we didn t give default constructor,instead we have

    so we can t use those default value,
    am i missing something here?

    thank you

    • nascardriver

      Hi Michael!

      These member variables should have been initialized with empty curly braces, because we don't access the values before writing to the variables. I've updated the lesson, thanks for pointing it out!

  • qwerty

    What's the difference between 'delete[]' and 'delete'? I'm guessing the first one should be used with arrays?

  • J34NP3T3R

    in Quiz 2b)

    is this a typo "overload" ?

  • kevin

    sorry duplicate post [plz delete it]

  • kevin

    Hi, in the last quiz 2c)

    isn't the use of '*' redundant, since 'auto' already takes care of that ?

    • nascardriver

      Using the * with `auto` is good practice, because it changes the meaning of `const`.

  • andrew

    Hi nascardriver,

    would love your feedback if you are available!

    some questions i have also:
    is there a preference for iterating with iterators (and dereferencing (*iter)-> or **iter) instead of range-based for loop?
    should we delete the dangling pointers in the vector after free-ing the memory?

    thank you in advance

  • goiu

    Hi there!
    In the last exercise, const auto* element shouldn't be resolved to "const Shape**"? Insted, it correctly resolves to "const Shape*"

  • yolo

    I think i might have some gaps.

    Can you explain about?
    auto *d{new Derived(5)}
    So far i have understood that d is a pointer which points to a space of a Derived(and Base) class with an instatiated value of 5.
    Not that it is actually pointing to a Derived object. Just some bytes in memmory that reprsent the size of Derived(5).
    Is my thinking flawed?

    • nascardriver

      That's correct

      • Yolo

        If it is true, then in the following snippet

        How can b return "Derived" when it is actually refering to a space of bytes in memmory and not in an actual Derived class?The word "new" is what troubles me.

        • nascardriver

          `d` points to a `Derived`, and so does `b`. Casting pointers or references doesn't affect the data they're pointing to.
          When you call `b->getName()`, the `getName()` function is looked up in the vtable of the object `b` is pointing to. Since `b` is pointing to a `Derived`, you're calling `Derived::getName()`.

  • yeokaiwei

    Thank you for the tutorial

    I hope this is acceptable.

    I managed to get the answer but I think I did a few things differently.

    Somehow, I got the answer.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]