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 -- Function templates
18.11 -- Printing inherited classes using operator<<

160 comments to 18.x — Chapter 18 comprehensive quiz

  • Tony Kolarek

    Done it with a normal for loop:

    I remember not being able to do this quiz previously and here I am again. Love it!

  • Glenn

    "Pure virtual functions can have a body, but they are still considered abstract."

    I think it would be useful to include here in the summary the important point that that body *can* exist but must be *separately* defined, outside the class definition, for the PVF still to be considered abstract.  (Presuming, of course, that I've understood this correctly!)

  • Glenn

    "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."

    Phew. And here I was, thinking I had actually made some progress!  ;-P

  • Gustaw

    So, I wrote something like that. And I have two questions:
    1. In line 79 I can't make x const, I'm getting error about conversion casting away constness.
    2. Line 79, 101 and 108 works even if I dont make x a pointer (x instead of *x). And my question is, because x is type Ishape*, adding * to x is just for easier reading or have some optimalization purposes? Or I just got it everything wrong?

    • nascardriver

      1. If you make `x` in line 79 `const`, you also need to make `c` in line 81 `const`. Otherwise you could modify the `const` `x` through the non-`const` `c`.

      2. `auto` can turn into a pointer. The asterisk changes what the `const` applies to

  • Phi Nguyen

    In question 2b solution, I don't think this block is correct, because class Point does not have Point() constructor:

  • salah


    in this line I am getting this error: "dynamic_cast cannot cast away const or other type qualifiers"

  • robinchaz

    for the ranged for loops, there is a * to explicitly say that the elements are pointers right?

    • nascardriver

      Correct. `auto` would turn into a pointer without the asterisk, so nothing would change. For clarity, we add the asterisk.

      • Kenny

        It is worth noting that declaring `auto*` is not only for clarity, but also necessary in this context of constantness. If the data type of the ranged-based for loops' element declarations was `const auto&` or simply `const auto`, then the variable of the element declarations would be a constant pointer, not a pointer to a constant value. That is,

        This consequence is understandable: how can the compiler infer a variable of type `const auto` is a pointer to a constant value without an explicit `*`?

        Pibben. (2019, October 8). Re: Does 'auto' type assignments of a pointer in c++11 require '*'? [Comment]. Stack Overflow. Retrieved from

  • slimeyzilla

    In solution 1(f)

    I am not really sure what it really means to dynamically allocate a Derived object. What parts of the object are dynamically allocated? The virtual table pointer? The offsets? All members? I am not sure what is going on here.

    • salah

      The same thing as you create a normal Derive object, the only difference is that normal object(without "new") is stored in "Stack memory".Whereas, using "new"  the object is stored in "Heap memory"

  • Mn3m


    Why didn't you pass the variables by reference in both of the 'for-each' loops inside main() ?

    • nascardriver

      These variable are pointers, which are fundamental types. Passing them by reference is slower.

      • Mn3m

        Yea, I learned from you guys that passing fundamental types, such as int/float...etc is faster when passed by values. But just to make sure that I understand, here in this example, we're passing pointers to objects not just objects on the stack, and that's why it's slower to pass them by reference?! Is that correct?

  • Fosterwerks

    Hello again! For the last quiz question: after deleting the shapes held by the vector, should we not also pop them off so we are not left with dangling pointers? :)

  • Ged

    What do you think of my function? I know it is not possible to have a radius below 0, but if lets say it was not a radius and just a number.

    • nascardriver

      When you don't modify pointer/reference parameter, make it `const`. This prevents stupid mistakes and makes your function usable with more arguments.
      `c` should be initialized with brace-initialization. You can also make its type `auto`, because the type is obvious from the initialization.

  • won-seok Kim

    Nice lecture so well watched.  the Shape class is interface class in the last quiz?

  • ErwanDL

    Hey nascardriver,

    In the last question of the quizz, what's the point of passing the pointers by reference when looping through the vector, since we are not mutating them anyway ?

    • nascardriver


      I don't think there is a point if doing this, but the comment makes me believe Alex did it on purpose. I'll leave the update to him just in case he wants to make a point.

      • Alex

        I'm not aware of any particular reason I did this intentionally. Sometimes the quizzes go through several iterations before settling on a final form -- that can lead to cruft from a prior approach sneaking into the final solution.

        I've removed the extraneous references.

  • Yep, `dynamic_cast` is the way to go here. If it's not available (Some projects don't use RTTI, which is required for `dynamic_cast`), you should use an `enum class`, not a string. String comparisons are slow and you can misspell them.
    I don't know if Alex covered it, but you can initialize `c` in the if-statement.

    > If you delete a dynamically allocated pointer, does it delete memory for all other pointers, that were pointing at the same memory address?
    You don't delete a pointer, you delete the object a pointer is pointing to. All pointers that pointed to the object are invalidated by the delete, ie. accessing them causes undefined behavior.

    • masterOfNothing

      I don't think I've seen it in any lesson so far. That's worthy of remembering. I didn't even know you could have more expressions inside the if clause, separated by a semicolon.

      Right, thanks.
      (sorry, for re-posting thing)

  • masterOfNothing

    I will not post the whole wall of code.
    The trickiest part was the

    At first I introduced a general getName() virtual function, so that in the getLargestRadius() function I could pick out the Circle object pointer from the std::vector to get the getRadius() function (btw I noticed the commented out getShapeName() in Shape class, and was wondering if it was there for the same reason):

    Then getLargestRadius() looked like this:

    The tutorial didn't mention anything about creating additional virtual member functions.
    So I started thinking (reading the notes I made) and remembered that the dynamic_cast failure results in a nullpointer.

    Good thing I write down notes (yes, physically on a paper, with a pen, on a big, fat notebook) of everything important I read in these tutorials.

    So the getLargestRadius() became:

    Not the cleanest code, could be cleaned up with less variables in it. (As the solution in the quiz suggests).

    I'm still unclear about deleting the dynamically allocated pointers. I had them in the function getLargestRadius() and it crashed. If you delete a dynamically allocated pointer, does it delete memory for all other pointers, that were pointing at the same memory address?

    e.g, this code from dynamic_cast lesson

    Does deleting Base *b object pointer influence Derived *d in this case?

    • Please edit your comments instead of re-posting them. Syntax highlighting works after refreshing the page. My reply is here

  • Torraturd

    Hi again, I got this answer for the final question

    • Hi

      - Initialize your variables with brace initializers.
      - `main`: Missing return-statement.
      - Pass/return non-fundamental types by reference.
      - `getRadius` should be const.
      - Line 77 should only run when the element is a `Circle*`. That reduces the size of `circle_vec`, improves its name and removes the check in line 81. Then you can use `std::max_element` to replace line 80+.
      - Line 100 should be a for-each loop, because you don't need the index.

  • Nirbhay


    I do not understand what this line in solution 1e) means.
    "Derived::getName() is an abstract function (with a body) and therefore can’t be instantiated."



  • DecSco

    Hi Alex,

    be const?

  • Daniel

    Hey guys, I have a question. Is there anything wrong / that should be avoided with my implementation of this function? Specifically, the method I used to check for nullptr.

    • Hey Daniel!

      - Inconsistent const reference notation (`const &` vs `& const`).

      `nullptr` can implicitly be converted to `bool`, you're method is fine. Personally I don't like implicit casts/comparisons. They make the code harder to understand, especially for beginners.

      You're casting `element` twice. Use the the if's initializer to create a `Circle`.

  • Ryan

    I tried something different. The error is: passing 'const Circle' as 'this' argument discards qualifiers for line // out << "Area of Circle: " << calculateArea() << "\n"; "

    Same for the virtual print in Square(). How to fix?

    Also what can be improved on in terms on better efficiency and display.

    • * Line 39, 54: Limit your lines to 80 characters in length for better readability on small displays.
      * Initialize your variables with brace initializers.
      * Line 63+: Initialize rather than push.
      * @print should be marked override.
      * Include <cmath> instead of <math.h>. <cmath> declared it's content in the @std namespace.
      * Use @std::acos instead of @acos.
      * Call @std::acos only once and store its result in a constexpr variable.

      Line 54: @print is const, meaning that @this is const and cannot be modified. You're calling @calculateArea, which is not const, and would be allowed to modify @this. Since @this is const, it cannot be used to call @calculateArea.
      Mark @calculateArea const.

  • Anthony

    Hi Alex,

    1) Is there a difference between 'auto const *r' and 'const auto *r'?
    2) Is it sensible in this case to use a pointer rather than a reference so that r can be set to nullpointer?

  • Spirit

    There is a missing semicolon after the newline character.

  • Spirit

    A typographical grammatical fallacy in "operator to directly specifying which classes"

  • Hello Alex,
    can i work with pointers only for this code

    i mean references is like taking a const pointer to each vector element right (which is a pointer)

    so this is not equivalent to:

    i need to make a pointer to pointer to match the first code correct ?

    • > element will be a const reference to a Shape*

      > i need to make a pointer to pointer to match the first code correct ?
      No. After compilation, references turn into pointers. Before that, you should look at them like aliases. @element is a

      EDIT: 1-line

      • i do not understand, i thought that const Shape* const &Elem is the same as auto const &element
        i understand after compilation references turn to pointers but doesn't that makes this case a pointer to another pointer,
        i think i'am confused as the pointer element is copied then deleted by another pointer or is deleted by the same pointer thru pointer to pointer ?

        • There's only 1 pointer here.
          Your first loop is equivalent to

          Calling @delete on a reference is the same as calling @delete on the object that is referenced by the reference.

          EDIT: 1-line

  • jason guo

    My solution.

    How come dynamic cast doesn't work on line 89?

    • * Line 23, 24, 25, 86, 87, 109: Initialize your variables with brace initializers. You used copy initialization.
      * Line 100, 101, 102: Initialize your variables with brace initializers. You used direct initialization.
      * Line 66, 67: Initialize your variables with brace initializers.
      * Line 106: Limit your lines to 80 characters in length for better readability on small displays.
      * @Triangle::Triangle: Use member initializer lists.
      * Enable compiler warnings, read them, fix them. (Lesson 0.10, 0.11)
      * Inconsistent formatting. Use the auto-formatting feature of your editor.
      * Inconsistent naming. Pick a naming convention, stick to it.
      * Line 89: Should use @dynamic_cast.

      > How come dynamic cast doesn't work on line 89?
      If the types are incompatible, @dynamic_cast returns @nullptr. Since @a stores pointer to objects that are not Cicles, at some point a nullptr will be returned. You're not checking if the cast was successful.

  • Dants


    In @getLargestRadius

    how exactly does the statement "Circle *c = dynamic_cast<Circle*>(element)" get evaluated as a boolean value ?


    • Hi Dants!

      It casts @c to a bool. Pointers evaluate to true if they are not null pointers. It's equivalent to

      Note: Brace initializers should be used

      The same can be done with all types that are convertible to bool.

      If you want your code to be more obvious, you can also use an initializer statement in the if:

  • lucieon

    In Quiz 2, what happens inside the default destructor of classes Triangle and Circle?
    I mean there is not any dynamically allocated memory in those classes so what's the point of making ~Shape() virtual?

    • Alex

      It's a best practice to have a virtual destructor if any functions in the class are virtual.

      That way, if you later derive from Shape, Triangle, or Circle, and do allocate dynamic memory (or do anything else that needs to be cleaned up) your derived class will be able to do so properly.

      • koe

        I do not believe 'default' destructors have been mentioned before this lesson. Is there some difference between a default destructor and an empty destructor?

        • nascardriver

          If `MyClass` has indestructible parts (Members/parents with an inaccessible destructor), the empty destructor is illegal. The defaulted destructor causes `MyClass` itself to become indestructible.
          The empty destructor is not trivial, which means that `MyClass` requires some special cleanup (via the destructor). A defaulted destructor doesn't prevent the destructor from being trivial (There are more conditions to really make the destructor trivial). When a destructor is trivial, it means that the only cleanup that needs to be done is freeing the memory of the object, nothing else.

  • Faruk

    Hi i coded a hangman game.I went very good.Any tips or code improvements.



    • When you don't have a reason not to, you should use what is supplied by the std library (@std::string).

      * Line 48, 63, 104, 118, 119, 123, 143: Initialize your variables with uniform initialization. You used copy initialization.
      * Line 7, 8, 14, 93, 94, 95: Initialize your variables with uniform initialization.
      * Code belongs is source files. Header files are for declarations.
      * Structs should not have member functions. That's what classes are there for.
      * Don't use @malloc, @free. Use @new, @delete.
      * @malloc, @memcpy, @free, @strlen should be replaced by their namespaced counterparts.
      * Missing include to @<cstring> for @std::malloc, @std::memcpy, etc.
      * @String::String(const String&) could be delegated to @String::String(const char*)
      * You're using a mix of C-functions and manual code (Not using @std::memcmp). Pick one, stick to it.
      * @String is missing assignment overloads
      * @String::Contains Parameters (except @index) should be const.
      * @String::Contains is specialized to your game. It should not be a member of @String.
      * Line 65: @this->Str_Buffer[i] doesn't have to exist.
      * Inconsistent use of @this
      * Use enum class instead of enum
      * Line 104: You don't have to specify the array size when initializing arrays.
      * Use C++ casts. (@static_cast, @reinterpret_cast, @dynamic_cast)
      * Magic number: 24
      * Use ++prefix unless you need postfix++
      * Don't use @system. "cls" is a Windows-exclusive command.
      * Don't compare booleans to false/true. They're booleans already.

      * Line 8, 27, 28, 36, 50, 60, 69: Initialize your variables with uniform initialization. You used copy initialization.
      * Line 15: Initialize your variables with uniform initialization.
      * @getRandomNumber::fraction should be constexpr
      * Use @std::rand, @std::srand, @std::time instead of their non-namespaced counterparts.
      * @std::time expects a pointer as it's argument. Pass @nullptr.
      * You're using the same name style for functions and variables. This can lead to confusion.
      * Line 32: Why initialize to a space?
      * Magic number: 8

      Please post your quiz solutions in future lessons so you don't get used to your current code style.

      • Faruk

        Thank you for your feed back.But i have a question about why should i use a class instead of a struct.Structs and Classes are the same thing the difference is the classes are private by default, and structs are public.

    • magaji::hussaini

      That was cool. Nice...

  • Arumikaze

    Hello! I am 99% sure I did everything correctly, but if you can please check my code I would greatly appreciate it. Thank you so much!






    Again, sorry for the long post. I am trying to get into the habit of having them in separate files.

    • Hi Arumikaze!

      Every header
      * If you're going to write definitions in header files, you might as well write the definitions inside the class. Write declarations in header files, definitions in source files. Default arguments should be part of the declaration.

      * I don't see the point of the @print function, you could've used @operator<< directly.
      * Good use of uniform initialization
      * Good use of dynamic memory
      * Good use of const/reference

      * @getLargestRadius: Have a look at @std::max_element

      std::max_element -

  • David

    Hi Alex! Thanks for the great quiz. Two quick questions:

    First, why do we need the continue statement in the getLargestRadius() function? I think the following is a little simpler:

    Second, does this part of your program only delete the Shape portion of each element of the vector? (i.e. are the Circle and Triangle parts leaked?)

    • Jack

      Obviously not Alex, just a fellow learner like you, so take what I say with a pinch of salt as may not be fully correct.

      1) I wrote my function the same way as you. I suppose the extra 'continue' can indicate clearer intention, but your version is by no means unclear. Same meat different gravy.

      2) Notice that in @Point:

      The vector is a vector of Shape*. Therefore we do not call the destructor of Shape, but the most-derived version of the destructor (that is, Circle or Triangle).

      As mentioned, I'm also just a learner, but that is what I understood of the lessons. Hopefully someone more knowledgeable can confirm.

Leave a Comment

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