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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <string_view> class Base { protected: int m_value; public: Base(int value) : m_value{ value } { } std::string_view getName() const { return "Base"; } int getValue() const { return m_value; } }; class Derived: public Base { public: Derived(int value) : Base{ value } { } std::string_view getName() const { return "Derived"; } int getValueDoubled() const { return m_value * 2; } }; |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> int main() { Derived derived{ 5 }; std::cout << "derived is a " << derived.getName() << " and has value " << derived.getValue() << '\n'; Derived &rDerived{ derived }; std::cout << "rDerived is a " << rDerived.getName() << " and has value " << rDerived.getValue() << '\n'; Derived *pDerived{ &derived }; std::cout << "pDerived is a " << pDerived->getName() << " and has value " << pDerived->getValue() << '\n'; return 0; } |
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!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> int main() { Derived derived{ 5 }; // These are both legal! Base &rBase{ derived }; Base *pBase{ &derived }; std::cout << "derived is a " << derived.getName() << " and has value " << derived.getValue() << '\n'; std::cout << "rBase is a " << rBase.getName() << " and has value " << rBase.getValue() << '\n'; std::cout << "pBase is a " << pBase->getName() << " and has value " << pBase->getValue() << '\n'; return 0; } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
#include <iostream> #include <string_view> #include <string> class Animal { protected: std::string m_name; // We're making this constructor protected because // we don't want people creating Animal objects directly, // but we still want derived classes to be able to use it. Animal(std::string_view name) : m_name{ name } { } // To prevent slicing (covered later) Animal(const Animal&) = delete; Animal& operator=(const Animal&) = delete; public: const std::string& getName() const { return m_name; } std::string_view speak() const { return "???"; } }; class Cat: public Animal { public: Cat(std::string_view name) : Animal{ name } { } std::string_view speak() const { return "Meow"; } }; class Dog: public Animal { public: Dog(std::string_view name) : Animal{ name } { } std::string_view speak() const { return "Woof"; } }; int main() { const Cat cat{ "Fred" }; std::cout << "cat is named " << cat.getName() << ", and it says " << cat.speak() << '\n'; const Dog dog{ "Garbo" }; std::cout << "dog is named " << dog.getName() << ", and it says " << dog.speak() << '\n'; const Animal *pAnimal{ &cat }; std::cout << "pAnimal is named " << pAnimal->getName() << ", and it says " << pAnimal->speak() << '\n'; pAnimal = &dog; std::cout << "pAnimal is named " << pAnimal->getName() << ", and it says " << pAnimal->speak() << '\n'; return 0; } |
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:
1 2 3 4 5 6 7 8 9 |
void report(const Cat &cat) { std::cout << cat.getName() << " says " << cat.speak() << '\n'; } void report(const Dog &dog) { std::cout << dog.getName() << " says " << dog.speak() << '\n'; } |
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:
1 2 3 4 |
void report(const Animal &rAnimal) { std::cout << rAnimal.getName() << " says " << rAnimal.speak() << '\n'; } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <array> #include <iostream> // Cat and Dog from the example above int main() { const auto cats{ std::to_array<Cat>({{ "Fred" }, { "Misty" }, { "Zeke" }}) }; const auto dogs{ std::to_array<Dog>({{ "Garbo" }, { "Pooky" }, { "Truffle" }}) }; // Before C++20 // const std::array<Cat, 3> cats{{ { "Fred" }, { "Misty" }, { "Zeke" } }}; // const std::array<Dog, 3> dogs{{ { "Garbo" }, { "Pooky" }, { "Truffle" } }}; for (const auto& cat : cats) { std::cout << cat.getName() << " says " << cat.speak() << '\n'; } for (const auto& dog : dogs) { std::cout << dog.getName() << " says " << dog.speak() << '\n'; } return 0; } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <iostream> int main() { const Cat fred{ "Fred" }; const Cat misty{ "Misty" }; const Cat zeke{ "Zeke" }; const Dog garbo{ "Garbo" }; const Dog pooky{ "Pooky" }; const Dog truffle{ "Truffle" }; // Set up an array of pointers to animals, and set those pointers to our Cat and Dog objects const auto animals{ std::to_array<const Animal*>({&fred, &garbo, &misty, &pooky, &truffle, &zeke }) }; // Before C++20, with the array size being explicitly specified // const std::array<const Animal*, 6> animals{ &fred, &garbo, &misty, &pooky, &truffle, &zeke }; for (const auto animal : animals) { std::cout << animal->getName() << " says " << animal->speak() << '\n'; } return 0; } |
While this compiles and executes, unfortunately the fact that each element of array “animals” is a pointer to an Animal means that animal->speak()
will call Animal::speak() instead of the derived class version of speak() that we want. The output is
Fred says ??? Garbo says ??? Misty says ??? Pooky says ??? Truffle says ??? Zeke says ???
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <array> #include <iostream> int main() { const Cat fred{ "Fred" }; const Cat misty{ "Misty" }; const Cat zeke{ "Zeke" }; const Dog garbo{ "Garbo" }; const Dog pooky{ "Pooky" }; const Dog truffle{ "Truffle" }; // Set up an array of pointers to animals, and set those pointers to our Cat and Dog objects const auto animals{ std::to_array<const Animal*>({ &fred, &garbo, &misty, &pooky, &truffle, &zeke }) }; // Before C++20, with the array size being explicitly specified // const std::array<const Animal*, 6> animals{ &fred, &garbo, &misty, &pooky, &truffle, &zeke }; for (const auto animal : animals) { std::cout << animal->getName() << " says " << animal->speak() << '\n'; } return 0; } |
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.
![]() |
![]() |
![]() |
Whenever i comment the page refreshes but then my comment does not display.
so i commented again and sometimes changing my wording to see if it will post but to no avail.
but a day later all of it appeared.
i sincerely apologize for this and i hope that you can just delete my comments below for spamming.
ALSO to QUESTION number one i copied the entire solution to question #1 and compiled it on my IDE
but its giving me an error
1>C:\Users\MY-PC\source\repos\MyPC\MyPC.cpp(58,30): error C2672: 'std::to_array': no matching overloaded function found
along with a bunch of errors including
1>C:\Users\MY-PC\source\repos\MyPC\MyPC.cpp(58,104): error C3422: 'std::array<remove_cv<_Ty>::type,_Size> std::to_array(_Ty (&)[_Size])': mismatched types 'const Animal' and 'const Cat'
`std::to_array` is a C++20 feature, you need to update your compiler or use the alternative approach, which is commented out in the solution.
how does this work ? i thought pointers only hold the memory address.
but in this code we did not have to dereference it with * and it works .. why ?
EDIT: trying again .. does not display
is an alternative (and clearer) way of saying
how does this work ? i thought pointers only hold the memory address.
but in this code we did not have to dereference it with * and it works .. why ?
how does this work ? i thought pointers only hold the memory address.
but in this code we did not have to dereference it with * and it works .. why ?
EDIT : i could not seem to comment ... it doesn't display
how does this work ? i thought pointers only hold the memory address.
but in this code we did not have to dereference it with * and it works .. why ?
in the first reason in "Use for pointers and references to base classes"
just a bit confused as to the reason to use this.
there is already a function in Animal called getName() that can be used by its Derived classes without having to use pointers.
and we cant call the derived::speak() using the pointer anyway. so why use a pointer ?
can we not achieve the same result with
i need to read this lesson one more time. its getting harrdddeerrr
i need to read this lesson one more time. its getting harder.
how does this work ? i thought pointers only hold the memory address.
but in this code we did not have to dereference it with * and it works .. why ?
The `->` operator performs the indirection for you, it's the same as
sorry about the spam. at the time i thought my internet is acting up because the messages are not posted.
If I had a destructor in the Animal class, would it also be "protected"?
At the end of the final example, there's a note that states, "...you can also make m_speak a std::string, but the downside of doing so is..."
However, the example has m_speak as a std::string. Am I missing something? The word "also" makes me think it's supposed to be different.
Note removed, thanks!
>> Why do we need an extra pair of curly braces in the second example and we'll get compile error without that? It doesn't make sense.
>>Would you elaborate on this slicing part a little more?
what does three iii mean here?
See section "Array of struct" in lesson P6.15
Slicing is covered later. I've adjusted the comments.
`iii` was a remnant from when this lesson didn't use a range-based for loop. Updated as well.
Thanks for pointing out issues in this lesson!
Moved to lesson 9.22
I have a few questions regarding Quiz question 1.
Why are copy constructor and assignment operator function "deleted"?
Secondly, why are they in protected?
And lastly, what's the worst that could happen even if they weren't "deleted" and were public?
As both of them (by default) do memberwise initialization, m_name being a std::string would make a copy for itself and m_speak being a std::string_view would point to the string literal which was available at compile-time. So where does the question of dangling reference/pointer occur?
> Why are copy constructor and assignment operator function "deleted"?
The comment at those functions was misleading. Deleting them prevents slicing.
> why are [copy constructor and assignment operator] in protected?
The public can't create or obtain `Animal` objects, so these functions don't need to be `public`.
> what's the worst that could happen even if they weren't "deleted" and were public?
The visibility doesn't matter, but slicing is dangerous. Changes highlighted with ###
`Cat::make` converts a `Cat` to `Animal`. When this happens, only `Animal`'s data members are copied (Because `Animal(const Animal&)` doesn't know what a `Cat` is. It can only copy its own members.).
`m_sound` is lost (It's sliced off). `Animal::m_speak` now dangles, accessing it (eg. through `Animal::speak()`) causes undefined behavior.
I've updated the lesson to use `std::string` for `m_speak`. This solved the undefined behavior issue, but slicing is bad nonetheless. Objects only make sense if they exist as a whole. Slicing is likely to break them.
You probably forgot to update the text below the code snippet along with the code, animals[iii]->speak() should be animal->speak() to conform to the for each loop in the code.
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.
> But before we discuss what virtual functions are, let’s first set the table for why we need them.
I like it ;)
"Since a Derived is-a Base"
should be
"Since Derived is-a Base"
I think?
mm well!! I did it this way
any suggestion
#include <iostream>
#include <string_view>
#include <string>
class Animal
{
public:
std::string m_name;
std::string m_speak;
// We're making this constructor protected because
// we don't want people creating Animal objects directly,
// but we still want derived classes to be able to use it.
Animal(const std::string &name,std::string speak)
: m_name( name ) ,m_speak(speak) {}
std:: string spe()const {return m_name+" says "+m_speak;}
//public:
// const std::string& getName() const { return m_name; }
//std::string speak() const { return "???"; }
};
class Cat: public Animal{
public:
Cat(const std::string &name,std::string speak) :Animal (name ,speak){}
// std::string speak() const { return "Meow"; }
};
class Dog: public Animal
{
public:
Dog(const std::string &name,std::string speak) :Animal (name ,speak){}
// std::string speak() const { return "Woof"; }
};
class Duck: public Animal
{
public:
Duck(const std::string &name,std::string speak) :Animal (name ,speak){}
// std::string speak() const { return "Woof"; }
};
int main()
{
Cat c("Cat","meow");
Cat c1("Cat","meow");
Dog p("Dog","wuauww");
Duck d("duck","cuak");
std::cout << c.spe();
std::cout<<std::endl;
std::cout << p.spe();
std::cout<<std::endl;
std::cout << d.spe();
std::cout<<std::endl;
Cat array[]={c,c1};
for(int i=0;i<2;i++){
std::cout << array[i].spe() <<'\n';
}
return 0;
}
Hello, thank you for this series of tutorials. They are excellent.
I'm a little confused about the answer of “Quiz time 1)” (line 56):
I do not understand the const means here ? and I tried to change the position of "const", and i also deleted "const" , The program can run normally(in visual studio 2019)
My question is what is the function of const in this sentence?
Thank you again for these tutorials.
The loop doesn't need to modify the elements. Just like when we mark arguments as `const` when passing a pointer or reference to a function, we do so for loop variables. That way we ensure that we don't accidentally modify something we didn't mean to.
thank you very much
=))) Really enjoyed this: "Want to take a guess what virtual functions are for? :)"
I really appreciate what you do & the effort to constantly update the examples & articles! Keep up the good work! Will certainly try to give back to you if I ever have the possibilities to.
God Bless and good luck!
Nonetheless, I do not understand what you meant with the second hint of the second question at the end of the article. And the first two sentences when I press show solution. Will come back to it, though. Am going to re-read the next chapter on virtual functions. Maybe that will clear my misunderstanding out.
Hey Nascardriver, there are also quite a few "const char*" strings remaining in this article as well. These could surely also be replaced with "std::string" or "std::string_view" as you said earlier (it's a good fit for replacing a const ref to an std::string, right ?).
Hello,
lesson (and the next lesson) modernized and applied your suggestion about the misleading "note: not virtual" comment. Thanks!
Hello, I love these tutorials and I have learned so much! I am just wondering if you know anywhere where I can learn about making databases for things like games. I can make simple ones with the fstream library but what I have found is that MySQL is probably a good one to learn and any information on a good place to learn that would be great. Thanks!
Hello sir,
Could you tell whether this code would be slower than the one where
is used, or would there be any chances of dangling references in this code:
Hello Udit!
* Initialize your variables with brace initializers.
* Line 13: Limit your lines to 80 characters in length for better readability on small displays.
* Line 35: No need for a reference, you can loop through the pointers directly.
* Don't use "using namespace".
* @Animal::getName and @Animal::speak should be const.
> Could you tell whether this code would be slower
Yes. @std::string is pretty much always slower than using C-style strings. The easier use of @std::string outweighs the performance benefits of const char*. Unless you're running on a system with highly limited resources, use @std::string.
> would there be any dangling references in this code
There are none.
Hi Alex! Two things:
1) Why don't you use const std::string&s for the constructor parameters? It works, and might just copy the whole object around less, but I am not sure exactly how it works. Can you maybe enlighten me?
2) After the stricter error levels you suggested, this solution will not compile yet again. You need to add
Here is my full code (split into a separate header for each class and updated with C++11 concepts like a for-each loop for exercise):
animal.h:
cat.h:
dog.h:
main.cpp:
Another question: I forgot to #include "animal.h" in cat.h and dog.h, yet the program still compiled and ran as expected. Why is that? I would've thought that would not compile?
1) You should pass by const reference.
> I forgot to #include "animal.h
#includes are handled by the preprocessor. It just copies the contents of the included file into the including file.
Since you're including @"animal.h" before "cat.h" and "dog.h", the declaration of @Animal exists when the declarations of @Cat and @Dog are encountered. If you include @"animal.h" after one of the other headers, you'll get an error.
Lesson updated. It is interesting to note that this style of initialization:
uses the copy constructor -- hence, if you delete the copy constructor, this won't compile any longer.
Hi Alex,
Why such an initializing use 'Copy constructor'? Wouldn't the compiler optimize that making it {"Fread", "Misty", "zeke"} ?
Hi Alex,
When we use "const char*" as the return type in speak() function, is the string(Either "???" or "Meow" or "Woof") created only once in memory?
Also if we use "const std::string&" as the return type, does deep copying still occur? or does it work just like "const char*"?
Maybe. C-style string literals have special handling, and identical C-style string literals may or may not be consolidated into a single entry.
If you return a const std::string&, then you'll end up returning a hanging reference (the C-style string literal would get implicitly converted into a std::string, which would go out of scope when the function returned). You'd have to return by value instead. Better to return the C-style string and let the caller convert to a std::string if that's what they want.
Hello,
In the quiz, doesn't use shallow copy ? even if are using string on speak ?
There isn't any copying going on in the quiz.
Also, std::string knows how to do a deep copy, so even if we did copy an object (using the default copy constructor), std::string would function correctly.
Or did I miss the point?
No you didn't, I thought i missed a point and i said to ask to make sure, but thank you.
Typo in Pointers, references, and derived classes:
Missing the backslash of '\n' in line 12. :)
Typo fixed, thanks!
I think there's a slight typo in relation to a misplaced comma:
"The problem, is of course, that because..." ---> "The problem is, of course, that because..."
Typo fixed. Thanks!
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.
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. :)
Why are you using const char* instead of const std::string*?
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.
Hi Alex ,
Any particular reason to use std::string for m_name?
I understand that the advantage of using const char* is that the same string literal may be consolidated into a single value for optimization purposes. But since m_name will be different for every animal, m_name can never enjoy this optimization.
Still, are there any reasons to use std::string (and return by std::string) for m_name? Since m_name will only be used for printing to the console, I don't see any reason not making it a const char* as well.
Thanks
Hi Dants!
@std::string is easier to use (eg. length, substring).
@std::string can be reassigned (eg. to rename an animal).
"yip"? Aren't you thinking of Ewoks here? :D
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.
"???" 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.
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.
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.