By default, derived classes inherit all of the behaviors defined in a base class. In this lesson, we’ll examine in more detail how member functions are selected, as well as how we can leverage this to change behaviors in a derived class.
When a member function is called on a derived class object, the compiler first looks to see if any function with that name exists in the derived class. If so, all overloaded functions with that name are considered, and the function overload resolution process is used to determine whether there is a best match. If not, the compiler walks up the inheritance chain, checking each parent class in turn in the same way.
Put another way, the compiler will select the best matching function from the most-derived class with at least one function with that name.
Calling a base class function
First, let’s explore what happens when the derived class has no matching function, but the base class does:
This prints:
Base::identify() Base::identify()
When base.identify()
is called, the compiler looks to see if a function named identify()
has been defined in class Base
. It has, so the compiler looks to see if it is a match. It is, so it is called
When derived.identify()
is called, the compiler looks to see if a function named identify()
has been defined in the Derived
class. It hasn’t. So it moves to the parent class (in this case, Base
), and tries again there. Base
has defined an identify()
function, so it uses that one. In other words, Base::identify()
was used because Derived::identify()
doesn’t exist.
This means that if the behavior provided by a base class is sufficient, we can simply use the base class behavior.
Redefining behaviors
However, if we had defined Derived::identify()
in the Derived
class, it would have been used instead.
This means that we can make functions work differently with our derived classes by redefining them in the derived class!
For example, let’s say we want derived.identify()
to print Derived::identify()
. We can simply add function identify()
in the Derived
class so it returns the correct response when we call function identify()
with a Derived
object.
To modify the way a function defined in a base class works in the derived class, simply redefine the function in the derived class.
This prints:
Base::identify() Derived::identify()
Note that when you redefine a function in the derived class, the derived function does not inherit the access specifier of the function with the same name in the base class. It uses whatever access specifier it is defined under in the derived class. Therefore, a function that is defined as private in the base class can be redefined as public in the derived class, or vice-versa!
Adding to existing functionality
Sometimes we don’t want to completely replace a base class function, but instead want to add additional functionality to it when called with a derived object. In the above example, note that Derived::identify()
completely hides Base::identify()
! This may not be what we want. It is possible to have our derived function call the base version of the function of the same name (in order to reuse code) and then add additional functionality to it.
To have a derived function call a base function of the same name, simply do a normal function call, but prefix the function with the scope qualifier of the base class. For example:
This prints:
Base::identify() Derived::identify() Base::identify()
When derived.identify()
is executed, it resolves to Derived::identify()
. After printing Derived::identify()
, it then calls Base::identify()
, which prints Base::identify()
.
This should be pretty straightforward. Why do we need to use the scope resolution operator (::)? If we had defined Derived::identify()
like this:
Calling function identify()
without a scope resolution qualifier would default to the identify()
in the current class, which would be Derived::identify()
. This would cause Derived::identify()
to call itself, which would lead to an infinite recursion!
There’s one bit of trickiness that we can run into when trying to call friend functions in base classes, such as operator<<
. Because friend functions of the base class aren’t actually part of the base class, using the scope resolution qualifier won’t work. Instead, we need a way to make our Derived
class temporarily look like the Base
class so that the right version of the function can be called.
Fortunately, that’s easy to do, using static_cast
. Here’s an example:
Because a Derived
is-a Base, we can static_cast
our Derived
object into a Base
reference, so that the appropriate version of operator<<
that uses a Base
is called.
This prints:
In Derived In Base
Overload resolution in derived classes
As noted at the top of the lesson, the compiler will select the best matching function from the most-derived class with at least one function with that name.
First, let’s take a look at a simple case where we have overloaded member functions:
For the call d.print(5)
, the compiler doesn’t find a function named print()
in Derived
, so it checks Base
where it finds two functions with that name. It uses the function overload resolution process to determine that Base::print(int)
is a better match than Base::print(double)
. Therefore, Base::print(int)
gets called, just like we’d expect.
Now let’s look at a case that doesn’t behave like we might expect:
For the call d.print(5)
, the compiler finds one function named print()
in Derived
, therefore it will only consider functions in Derived
when trying to determine what function to resolve to. This function is also the best matching function in Derived
for this function call. Therefore, this calls Derived::print(double)
.
Since Base::print(int)
has a parameter that is a better match for int argument 5
than Derived::print(double)
, you may have been expecting this function call to resolve to Base::print(int)
. But because d
is a Derived
, there is at least one print()
function in Derived
, and Derived
is more derived than Base
, the functions in Base
are not even considered.
So what if we actually want d.print(5)
to resolve to Base::print(int)
? One not-great way is to define a Derived::print(int)
:
While this works, it’s not great, as we have to add a function to Derived
for every overload we want to fall through to Base
. That could be a lot of extra functions that essentially just route calls to Base
.
A better option is to use a using-declaration in Derived
to make all Base
functions with a certain name visible from within Derived
:
By putting the using-declaration using Base::print;
inside Derived
, we are telling the compiler that all Base
functions named print
should be visible in Derived
, which will cause them to be eligible for overload resolution. As a result, Base::print(int)
is selected over Derived::print(double)
.
This is probably not very relevant in practice, but interesting nonetheless:
A function in the derived class will be preferred by the compiler even if it's private and a matching overload in the base class is public, leading to a potential compiler error. On a similar note, the compiler will choose a better-matching private overload over a worse public overload in the same class and refuse to compile.
If the derived class doesn’t define a function, the base version is used.
If the derived class defines the function, it hides all base versions with the same name—even if the overloads differ.
Even if the overloads in the base class are better matches, once a derived class defines a function of the same name, the base versions are hidden unless explicitly reintroduced.
Use Base::function() to call the base version inside a derived override to extend rather than replace behavior.
When we use a using-declaration in
Derived
to make allBase
functions with a certain name visible withinDerived
, are there any consequences to having multiple definitions of similar function overloads?Referring to
void print(double)
in the final code of the lesson where we made allBase::print()
functions eligible for overload resolution, will the compiler remove all parent versions of the overload with the same signature so that someone using the Derived public interface that callsprint(double)
will only resolve to the version that belongs to theDerived
class? In other words, function overloads of the same signature from the parent class will be lost to (only to) the user calling theDerived
public interface?Edit: I guess not, I realised
Derived.Base::print(double)
still works so the compiler did not remove it. Anyhow are there side effects we should be wary of?Only that it may result in a better matching overload being pulled in.
With
using Base::foo
commented out,d.foo(5)
callsDerived::foo()
. If we uncommentusing Base::foo
because we wantBase::foo(char)
to be accessible,d.foo(5)
now callsBase::foo(int)
as it is a better match.When there is the same overload for a function (like in the example in the lesson, where there are two print(double) functions), how do we know which one gets called? Does the Derived function get priority over the Base?
Methods from derived classes shadow the methods from the superclasses, as it was stated in the article - redefinition of the function in derived class is superior when called from derived class. Consider following:
The output is of course
Derived foo(double)
because if derived class reimplements a method of base class with the same signature, this method will shadow the method from base class, even if it was made visible via using.I have the same question. I asked AI and it told me that the function in the Derived class will override the ones with same signature in the Base class. Only all the functions in the Derived and those in the Base which are not hided will participate in the resolution of overloading.
Hope Alex will give us an answer.
"So what if we actually want d.print(5) to resolve to Base::print(int)? One not-great way is to define a Derived::print(double):"
I think you meant "... is to define a
Derived::print(int)
" asDerived::print(double)
is already defined.This is definitely superior to my work around. Thanks!!
Thanks for the correction! Integrated.
Very helpful thank you for your efforts
why do i have to declare pri_base as a friend in both classes in order to access m_base
The first friend declaration gives pri_base access to the private members of base. The second one implements the function as a non-member (friend functions aren't members) that has access to the private members of derived.
Since pri_base doesn't need access to the private members of derived, it shouldn't be defined there. Either define it in base, or make it a non-member.
Searched the web how to call the base classes operator<< for 10 minutes just to see it appearing in the next scroll down :(
pat pat
I have a question .When we call Base::identify() , what is the argument for the hidden "this" pointer ????
Ohh sorry Alex , I realized it now , the "this" pointer of the caller ("identify") is the argument .
Yes, the value of "this" is the address of the object the member function is being called on.
There's a comment above:
"Calling function identify() without a scope resolution qualifier would default to the identify() in the current class, which would be Derived::identify(). This would cause Derived::identify() to call itself, which would lead to an infinite loop!"
This isn't an infinite loop, but an infinite recursion, and would likely cause the program to crash from blowing the stack. Since recursion was covered in an earlier section, this can probably be clarified.
Thank you for all of the effort you put into this site -- this has been a gold mine for me.
I agree, recursion is a better term. Updated. Thanks!
Sorry if it’s a stupid question but how static_cast does convert from Derived to Base? Is it just reference to a fenced Derived object which doesn’t show non-Base members and is seen as Base?
A Derived object static_cast to a Base& uses the Base class interface (but is actually a Derived object, so virtual functions will still resolve to Derived functions). This means Derived portions of the class are inaccessible except through virtual function resolution.