Search

12.x — Chapter 12 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 specifying 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 assigning it to 0. 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”.

1a)

Show Solution

1b)

Show Solution

1c)

Show Solution

1d)

Show Solution

1e)

Show Solution

1f)

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

13.1 -- Function templates
Index
12.10 -- Printing inherited classes using operator<<

57 comments to 12.x — Chapter 12 comprehensive quiz

  • In 12.3 it says "You should always make your destructors virtual if you’re dealing with inheritance" but it didn't fully tell me why. If you have a destructor with a non-empty body, then making it virtual makes sense. But it doesn't touch on the case where the body is empty but we still want the destructors to be virtual.

    Hit into this problem first time in exercises 1f and 2, where the body is empty. So why would I even care if the most derived destructor gets called then? Here (https://msdn.microsoft.com/en-us/library/6t4fe76c.aspx) it says:

    > If a base class or data member has an accessible destructor, and if a derived class does not declare a destructor, the compiler generates one. This compiler-generated destructor calls the base class destructor and the destructors for members of the derived type.

    So somehow it seems that only the most derived destructor can properly clean up its own members (that the base class is unaware of).

    From same article:
    > When an object goes out of scope or is deleted, the sequence of events in its complete destruction is as follows:
    > 1. The class's destructor is called, and the body of the destructor function is executed.
    > 2. Destructors for nonstatic member objects are called in the reverse order in which they appear in the class declaration [...]
    > 3. Destructors for nonvirtual base classes are called in the reverse order of declaration.
    > 4. Destructors for virtual base classes are called in the reverse order of declaration.

    Step 2 is unclear to me. Why can't it call Base::~Base() in step1 (so no virtual table) and still be able to do step 2. Probably it's compiler specific, since the same article also mentions:

    > This "most-derived object" deallocation is guaranteed to work only with virtual destructors. Deallocation may fail in multiple-inheritance situations where the type information does not correspond to the underlying type of the actual object.

    Any more info around this would be nice, but probably I'm not going to have this cleared up until I understand the details of how the compiler does it :).

    • Alex

      The first example in 12.3 shows why you should make your destructors virtual -- if your destructor isn't virtual, then only the base destructor may be called, which means your object may not be cleaned up as intended (if it's reliant upon a derived destructor to do some cleanup). Generally we want all of the destructor in the inheritance chain to execute, to give each part of the object the chance to clean up whatever it manages.

      A base class destructor might be empty, but it's not always possible to predict who is going to inherit from your class later, and whether that class will need to have its destructor called.

      Member objects in a class are typically constructed in order of declaration, so they are destroyed in the opposite order, just in case one member has a dependency on another member declared previously (which is generally a bad idea).

      To your last point, it's the same answer as the first point: if your destructor isn't virtual, then only one destructor will get called, not all of the destructors in the inheritance chain. I'm not sure about the point around multiple-inheritance.

  • warchiefbinar

    This is a very good C++ tutorial. Thank you very much. Things are getting clearer and clearer for me avery day. I still do have problems with understanding casting but I'm on my way. Thanks again!

    • Alex

      Casting is fairly straightforward:
      * static_cast is used to convert one type to another type (e.g. a float to an int, or vice versa).
      * dynamic_cast is used to downcast a base type pointer or reference to a derived type pointer or reference, with type checking performed.

      static_cast can also be used to downcast a base type pointer or reference to a derived type pointer or reference without type checking.

  • Frederico

    On question 1e, can you define the body of an abstract function inside the class? I thought it had to be done outside.

    • Alex

      No, it needs to be defined outside the class definition (though some compilers will let you do it inside). I've fixed the answer. Thanks for pointing that out!

  • Frederico Machado

    That's a lot of information so far. I feel I need to read the past two chaptes again to let it all sink in. I'm hoping the next chapters don't get too complicated.
    5 chapters to go.
    By the way, this tutorial is great. Thank You so much. It explains things in such a way that even less  fortunate people intellectually, like me, can understand. Without being superficial.

  • Benjamin

    Hi,

    one question about 2c):

    I implemented the solution just the way you did (Whew) with one minor difference in this line:

    I declared the circle-pointer in front of the loop and assigned the new address given by the dynamic cast in each iteration. I thought this might be better, as it saves the multiple creations and destructions of the pointer. Is that right?

    And another remark: I would sleep better, if you changed the triangle and circle to 2D shapes. A triangle can be defined by 3 points in 3D space, but a circle additionally needs 2 angles for its orientation to be completely defined in 3D 😉

    • nascardriver

      Hi Benjamin!
      A pointer is the same as an int in memory, there is no real creation or destruction.
      I wrote a minimal sample to see if declaration inside or outside the loop changes anything.

      I compiled this code with gcc 7.2.0 and loaded the produced binary in a disassembler.
      The asm looks rather complicated at the first glance, I added some comments to make it understandable without asm knowledge.

      The code is identical for both functions.

      What's going on?
      rax is a register, registers are there when your program starts until your program exits, they don't need to be created or removed.
      s_arr[s] is copied in such a register, in other words, there is no variable that needs to be created.

      • Benjamin

        Hi,

        thanks for your answer. I was aware that a pointer is not a type that takes a big chunk of memory. but just an address. After all this is the sense of using pointers or references. By "creation and destruction" i basically meant that the pointer goes out of scope for each iteration and i imagined it will be put on the stack, taken from the stack, put on the stack, taken from the stack and so on...

        So your analysis means that the compiler is smart enough to reserve such a register for the s_arr[s] array and keep it reserved no matter whether the pointer goes out of its block scope or not?

        • nascardriver

          Yes.
          This of course depends on the compiler, but I assume most compilers are smart enough not to push/pop every cycle.

        • Alex

          When a function is called, a new stack frame is pushed on the call stack. This stack frame typically contains memory for all of the local variables allocated inside the function (regardless of where they are defined within the function). Then the compiler need only enforce the appropriate scoping rules, which it can do as part of the compilation process.

          So the memory for Circle *c is allocated when the function is called, leading to no performance difference whether it is defined inside or outside the loop block.

          That said, under the best practice that variables should be defined in as small a scope as possible, it's better to put Circle *c inside the block.

  • Luhan

    I was wondering if all circle and triangles have a shape, and they have a point too, so how could we inherite these 2 without conflicting the overloaded operator<< (because when I tried to inheret these two, there was a conflict with the overloaded operator).

    • Alex

      I don't understand the question. operator<< is only defined in the Shape class. Having Triangle or Circle inherit Shape doesn't change the fact that there's only one flavor of the function...

      • Luhan

        I mean the class point

        I  was trying to say that every shape has a point, so for me would make sense to inherit it, but that's causing a conflict with the overloaded operator<<. So I wanted to know if there is a way to disambiguate when using the operator.

        • Alex

          A shape has-a point, not is-a point, so Shape should be using composition, not inheritance.

          Even so, if Shape were to inherit Point, there should be no conflict between operator<< for Shape and for Point because they are distinct classes.

  • Angmar

    Also, How is v.clear(); different from the code below?

    • Alex

      v.clear() removes all of the elements from the vector. But it does not delete the elements.

      Our loop explicitly deletes the elements (leaving the vector holding dangling pointers, which are cleaned up when the vector goes out of scope)

  • Angmar

    Hi. When compiling, my compiler warns me that the classes have no out-of-line virtual method definitions; their vtables will be emitted in every translation unit. What am I doing wrong?

    • Alex

      If all of your virtual function definitions are inside the class definition, the compiler doesn't know which file to place a single shared copy of the virtual table for the class -- so it puts the vtable in each file. In most cases, the linker should resolve the redundancy, so this will mostly likely just result in some object file bloat.

  • Steve

    Hey Alex,
    Thank you for the tutorials.
    For the last question, when deleting all the elements of the vector I have done this:

    But it keeps giving me an error message saying "deleting object of abstract object Shape will cause undefined behavior". Why is this?

  • Ray

    Hi Alex, your guide has been a great help in teaching me c++ so far.
    I've been wondering though, as I go through the tests, is there a reason to use regular for statements vs for each statements, in situations such as the quiz on this chapter?
    Thanks for all your hard work!

  • Eric

    Hi, Alex
    I don't understand pure virtual functions at all.
    Why do we need them? It's like if you make any random function equal in that class to 0 then
    that class cannot be created in main function or elsewhere as object, but can be inherited to other classes?
    And can you call pure function as normal?  
    And there was something about interface, what is it and when its used?

    Oh, and about vectors, do they store their elements in heap or stack?
    Sorry for taking your time with so many questions,
    Thanks

    • Alex

      An Interface class is a class that can't be instantiated itself. Instead, it declares one or more pure virtual functions, with the expectation that those functions will be defined by classes that inherit the interface. So we can say that all classes that are derived from the Interface class implement the interface (or are interfaces themselves if they don't)

      Once we have an Interface class, due to how polymorphism works, we can then write functions or arrays that work against the interface class, and have it work with ANY class derived from that interface.

      For example, we could write a function like this:

      This function will work with any class derived from Shape. How many points does a Shape have? Who knows, because we don't know what type of Shape it is. But if we did this:

      The Triangle class is now a real class that can be instantiated. And we can pass a Triangle object to our printNumberofPoints() function, and it will print 3.

      In this case, we don't have to write a version of printNumberofPoints() for EVERY possible shape in existence. We write it for a single generic shape, and it will work for any shape we can dream up thereafter.

      Vectors allocate their memory on the heap, as they do so dynamically. All dynamic allocations happen on the heap.

  • Cunni

    For this

    It only deletes the pointers right? If we also wanted to delete the object instances, we would do something like this:

    Is my understanding correct?

    • Alex

      No. delete v[i] deletes the object instance pointed to by v[i].

      The v[i] pointer will get deleted when the v array goes out of scope.

      There are very few cases in which you would ever need to call a destructor explicitly.

  • Mohsen

    Hi Alex. Again, thank you for this amazing tutorial.
    My getLargestRadius() and main() are little different. It works fine but i want your opinion.

    • Alex

      Your method is a little inefficient (performance and redundancy-wise) since you're doing the dynamic_cast up to 3 times per iteration. This could be optimized. But it's otherwise fine.

    • Dani

      I'd say there are parts that are a conceptual error

      In this section of code you're dynamic casting all the shape pointers, I guess that in order to properly print as a Circle or Triangle.
      But while implementing Shape, we've defined operator<< and print in order to be able to use a Shape pointer and make use of the child version of print instade of Shape's one.

  • Sean Kelly

    Good afternoon Alex,

    just wondering if pop_back() is the same as delete in terms of pointers
    just being removed from the vector and still taking memory or being deleted
    all together. I'm assuming it's the former.

    Thanks!

    • Alex

      pop_back does not do a delete. If you have a vector of pointers and you do a pop_back, you will need to delete the pointer subsequently.

  • Jasur

    when we print vector  

    it prints address.
    While

    in prints as necessary.
    Can you explain it why, please?

    • Alex

      Vector v is a vector of pointers to Shapes. So v[i] is a single pointer to a Shape. So if you print v[i], you're printing the address of the Shape, not the actual Shape. To print the actual shape, we need to dereference the pointer, which is *v[i].

  • C++ Learner

    why are we calling the function always with base class what is the advantage of that?

    • Alex

      In a program like this, there is none. However, it's to teach you the principles that you can then apply to other programs that are useful. For example, you could have an array of pointers to Base objects -- however, these could actually point to Derived objects or anything else derived from Base. This allows us to create arrays that store classes of different types, as long as they're all derived from a common parent.

  • Chris

    Hey Alex,
    I tried to move each class in a different header and cpp file. But if i include everything each class needs in its own header i will have for instance multiple inclusions of the class "Shape.h" in the final program. Should i just include nothing in each header so that the classes only work if i include them in the correct order in the main.cpp file or i should i make them work independently and let the header guards do the work ?

    • Alex

      Each header and code file should be as independent as possible, and take care of all of its own dependencies. It's okay to have multiple files including Shape.h. If you ever get into a situation where the order of the #includes matters, you've probably done something wrong.

  • Gapo

    I've done it like this

  • Hugh Mungus

    Hey Alex,
    Thanks for putting the effort in maintaining this site.

    I might have cheated a little bit in the last question.

  • loveu

    Qn 1a) base class is missing a const in the getName() function

  • Elruin

    There is no return in main() in last quiz.

  • ali

    hello, Alex, I tried something in the getLargestRadius()

    the out put for this in main

    is:
    not circle this is triangle it didn't has a radius
    The largest radius is: 7
    why not it be:
    the largest radius is : not circle this is triangle it didn't has a radiusn
    7
    ?

    • Alex

      This happens because the std::cout in main() and in getLargestRadius() are independent.

      When the compiler encounters this:

      It first evaluates getLargestRadius(). This prints "not circle this is triangle..." and returns the value 7. At that point, the rest of the expression evaluates as if you had typed:

      So it's clear that the "not a circle" text executes before the "The largest radius" text.

  • Philip

    In the last paragraph before the quiz you have operator<lt instead of operator<<.

  • Tuan

    The quiz 1f is hide under solution of quiz 1e.
    Anw, thanks for great tutorial, Alex.

  • Prado

    You forgot to #include <vector> in the last quiz question

Leave a Comment

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