Search

12.1 — Pointers and references to the base class of derived objects

In the previous chapter, you learned all about how to use inheritance to derive new classes from existing classes. In this chapter, we are going to focus on one of the most important and powerful aspects of inheritance -- virtual functions.

But before we discuss what virtual functions are, let’s first set the table for why we need them.

In the chapter on construction of derived classes, you learned that when you create a derived class, it is composed of multiple parts: one part for each inherited class, and a part for itself.

For example, here’s a simple case:

When we create a Derived object, it contains a Base part (which is constructed first), and a Derived part (which is constructed second). Remember that inheritance implies an is-a relationship between two classes. Since a Derived is-a Base, it is appropriate that Derived contain a Base part.

Pointers, references, and derived classes

It should be fairly intuitive that we can set Derived pointers and references to Derived objects:

This produces the following output:

derived is a Derived and has value 5
rDerived is a Derived and has value 5
pDerived is a Derived and has value 5

However, since Derived has a Base part, a more interesting question is whether C++ will let us set a Base pointer or reference to a Derived object. It turns out, we can!

This produces the result:

derived is a Derived and has value 5
rBase is a Base and has value 5
pBase is a Base and has value 5

This result may not be quite what you were expecting at first!

It turns out that because rBase and pBase are a Base reference and pointer, they can only see members of Base (or any classes that Base inherited). So even though Derived::getName() shadows (hides) Base::getName() for Derived objects, the Base pointer/reference can not see Derived::getName(). Consequently, they call Base::getName(), which is why rBase and pBase report that they are a Base rather than a Derived.

Note that this also means it is not possible to call Derived::getValueDoubled() using rBase or pBase. They are unable to see anything in Derived.

Here’s another slightly more complex example that we’ll build on in the next lesson:

This produces the result:

cat is named Fred, and it says Meow
dog is named Garbo, and it says Woof
pAnimal is named Fred, and it says ???
pAnimal is named Garbo, and it says ???

We see the same issue here. Because pAnimal is an Animal pointer, it can only see the Animal portion of the class. Consequently, pAnimal->speak() calls Animal::speak() rather than the Dog::Speak() or Cat::speak() function.

Use for pointers and references to base classes

Now you might be saying, “The above examples seem kind of silly. Why would I set a pointer or reference to the base class of a derived object when I can just use the derived object?” It turns out that there are quite a few good reasons.

First, let’s say you wanted to write a function that printed an animal’s name and sound. Without using a pointer to a base class, you’d have to write it using overloaded functions, like this:

Not too difficult, but consider what would happen if we had 30 different animal types instead of 2. You’d have to write 30 almost identical functions! Plus, if you ever added a new type of animal, you’d have to write a new function for that one too. This is a huge waste of time considering the only real difference is the type of the parameter.

However, because Cat and Dog are derived from Animal, Cat and Dog have an Animal part. Therefore, it makes sense that we should be able to do something like this:

This would let us pass in any class derived from Animal, even ones that we created after we wrote the function! Instead of one function per derived class, we get one function that works with all classes derived from Animal!

The problem, is of course, that because rAnimal is an Animal reference, rAnimal.speak() will call Animal::speak() instead of the derived version of speak().

Second, let’s say you had 3 cats and 3 dogs that you wanted to keep in an array for easy access. Because arrays can only hold objects of one type, without a pointer or reference to a base class, you’d have to create a different array for each derived type, like this:

Now, consider what would happen if you had 30 different types of animals. You’d need 30 arrays, one for each type of animal!

However, because both Cat and Dog are derived from Animal, it makes sense that we should be able to do something like this:

While this compiles and executes, unfortunately the fact that each element of array “animals” is a pointer to an Animal means that animals[iii]->speak() will call Animal::speak() instead of the derived class version of speak() that we want.

Although both of these techniques could save us a lot of time and energy, they have the same problem. The pointer or reference to the base class calls the base version of the function rather than the derived version. If only there was some way to make those base pointers call the derived version of a function instead of the base version…

Want to take a guess what virtual functions are for? 🙂

Quiz time

1) Our Animal/Cat/Dog example above doesn’t work like we want because a reference or pointer to an Animal can’t access the derived version of speak() needed to return the right value for the Cat or Dog. One way to work around this issue would be to make the data returned by the speak() function accessible as part of the Animal base class (much like the Animal’s name is accessible via member m_name).

Update the Animal, Cat, and Dog classes in the lesson above by adding a new member to Animal named m_speak. Initialize it appropriately. The following program should work properly:

Show Solution

2) Why is the above solution non-optimal?

Hint: Think about the future state of Cat and Dog where we want to differentiate Cats and Dogs in more ways.
Hint: Think about the ways in which having a member that needs to be set at initialization limits you.

Show Solution

12.2 -- Virtual functions and polymorphism
Index
11.x -- Chapter 11 comprehensive quiz

74 comments to 12.1 — Pointers and references to the base class of derived objects

  • Zixin

    Thanks for this great guide! Alex, I suggest you to consider having some kind of a membership for learners who dont like ads, instead of everyone just installing Ads Blocker. Without Ads, this page is clean and more comfortable to read.

    • Alex

      Thanks for the suggestion! I've thought about it. But having a membership means the site now has to deal with:
      * User accounts
      * Billing
      * Customer support

      That adds a lot of complexity to what is already a fairly time-intensive hobby site. 🙂

  • Nick Eh 30

    Why are you using const char* instead of const std::string*?

    • Alex

      To avoid having to create a std::string object every time the function is called. The user can always construct a std::string from the const char* return value if they want to work with or manipulate the string further.

  • DecSco

    "yip"? Aren't you thinking of Ewoks here? 😀

  • An unrelated question about the return type used for the speak() function.

    const char* speak() { return "???"; }

    Alex, is there any reason why you use C-style const char* instead of const std::string? Is 'string' inefficient here because that would return by-copy? I'm aware that we can't return by reference here.

    • Alex

      "???" is of type const char*, so that's what the function returns (to avoid conversions). Using std::string as a return value would work, but it would cause a std::string to get constructed every time this function was called, which is inefficient.

  • Ran

    Is this an explanation of 12.2.2 ?

    The programmer will encounter the difficult in constructing the
    derived classes. For example, one wants to add a private member in the
    Cat class:

    If you construct the Cat instance like the following code:

    Then the code will not work as you expected. You will have hard time
    in constructing a private member in the Cat class.

  • Ran

    Hi I just want to verity this performance of two kinds of string. I
    sometimes encounters the cases where C-style is weaker than
    std::string. Although most of the time, C-style is faster, but only a
    margin.

    =========================================
    Fred says Meow
    Garbo says Woof
    Misty says Meow
    Pooky says Woof
    Truffle says Woof
    Zeke says Meow
    Time elapsed:(Std::string) 0.00173975
    Fred says Meow
    Garbo says Woof
    Misty says Meow
    Pooky says Woof
    Truffle says Woof
    Zeke says Meow
    Time elapsed:(CStyle) 0.00174155
    Press any key to continue . . .
    ======================================

    This is my test code. In case someone may interested in.

Leave a Comment

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