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.
Calling a base class function
When a member function is called with a derived class object, the compiler first looks to see if that member exists in the derived class. If not, it begins walking up the inheritance chain and checking whether the member has been defined in any of the parent classes. It uses the first one it finds.
Consequently, take a look at the following example:
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 |
class Base { protected: int m_value; public: Base(int value) : m_value(value) { } void identify() { std::cout << "I am a Base\n"; } }; class Derived: public Base { public: Derived(int value) : Base(value) { } }; int main() { Base base(5); base.identify(); Derived derived(7); derived.identify(); return 0; } |
This prints
I am a Base I am a Base
When derived.identify() is called, the compiler looks to see if function identify() has been defined in the Derived class. It hasn’t. Then it starts looking in the inherited classes (which in this case is Base). 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!
In our above example, it would be more accurate if derived.identify()
printed “I am a Derived”. Let’s modify 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Derived: public Base { public: Derived(int value) : Base(value) { } int getValue() { return m_value; } // Here's our modified function void identify() { std::cout << "I am a Derived\n"; } }; |
Here’s the same example as above, using the new Derived::Identify() function:
1 2 3 4 5 6 7 8 9 10 |
int main() { Base base(5); base.identify(); Derived derived(7); derived.identify(); return 0; } |
I am a Base I am a Derived
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!
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 |
class Base { private: void print() { std::cout << "Base"; } }; class Derived : public Base { public: void print() { std::cout << "Derived "; } }; int main() { Derived derived; derived.print(); // calls derived::print(), which is public return 0; } |
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. 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 (the name of the base class and two colons). The following example redefines Derived::identify() so it first calls Base::identify() and then does its own additional stuff.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Derived: public Base { public: Derived(int value) : Base(value) { } int GetValue() { return m_value; } void identify() { Base::identify(); // call Base::identify() first std::cout << "I am a Derived\n"; // then identify ourselves } }; |
Now consider the following example:
1 2 3 4 5 6 7 8 9 10 |
int main() { Base base(5); base.identify(); Derived derived(7); derived.identify(); return 0; } |
I am a Base I am a Base I am a Derived
When derived.identify()
is executed, it resolves to Derived::identify(). However, the first thing Derived::identify() does is call Base::identify(), which prints “I am a Base”. When Base::identify() returns, Derived::identify() continues executing and prints “I am a Derived”.
This should be pretty straightforward. Why do we need to use the scope resolution operator (::)? If we had defined Derived::identify() like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Derived: public Base { public: Derived(int value) : Base(value) { } int GetValue() { return m_value; } void identify() { identify(); // Note: no scope resolution! cout << "I am a Derived"; } }; |
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!
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:
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 |
class Base { private: int m_value; public: Base(int value) : m_value(value) { } friend std::ostream& operator<< (std::ostream &out, const Base &b) { out << "In Base\n"; out << b.m_value << '\n'; return out; } }; class Derived : public Base { public: Derived(int value) : Base(value) { } friend std::ostream& operator<< (std::ostream &out, const Derived &d) { out << "In Derived\n"; // static_cast Derived to a Base object, so we call the right version of operator<< out << static_cast<Base>(d); return out; } }; int main() { Derived derived(7); std::cout << derived; return 0; } |
Because a Derived is-a Base, we can static_cast our Derived object into a Base, so that the appropriate version of operator<< that uses a Base is called.
This prints:
In derived In base 7
![]() |
![]() |
![]() |
in the section: "Redefining behaviors"
i thought because we made it the opposite as private we can use the print from the base class as it can't find the print from the derived class
Hi Michael!
Functions are resolved before their access modifiers are taken into account. Your compiler sees that you want to call derived.print, it finds @Derived::print, it stops searching. Only after this has happened, the compiler checks if you actually have access to @Derived::print, which you don't, so it errors out.
I would like to ask you about polymorphism.
Redefining behaviors - it's means: Creating functions, which overide function in base class.
Could tell me if I create functions without virtual keyword, does it belong to polyformism?
I know, that base ptr or ref, call only base functions (not dervied).
I'd say no, as it means the derived class can't be properly substituted for the base class and still be functional as intended.
I have advanced a little more in the tutorial and now I think in the line
we are doing upcasting and we are using anonymous sliced object to be able to use the friend function in the base class.
Quote:
"To modify a function the way a function defined in a base class works in the derived class, simply redefine the function in the derived class."
I believe you meant:
"To modify the way a function defined in a base class works in the derived class, simply redefine the function in the derived class."
I did indeed. Thanks for pointing out the typo.
why we use static cast?
To convert a value from one type to another. See lesson 4.4a on explicit type conversion (casting).
Don't we then convert from bigger type to a smaller type? Isn't, there, data loss then?
It depends on the context. If you static cast a double value to a float value, then yes. If you static cast a derived class pointer or reference to a base class pointer or reference, then no.
Hi Alex, I don't understand why we are using the 'using' keyword when redefining the access specifier on inherited members.
why do we use 'using'. Also, why does c++ not include the braces when re-specifying access of a member function i.e.
Thanks for the help :).
In this context, the using keyword tells the code that we want to use the base class function at this new access level. C++ doesn't seem to like to create new keywords (that may cause naming conflicts with existing programs) so they often overload existing ones.
I'm guessing the parenthesis are not needed because you don't need to respecify the parameters. Having empty parenthesis implies no parameters, which isn't necessarily the case.
@Alex
In the "Redefining functionality" section, I think this line needs review:
"Therefore, a function that is defined as private in the base class can redefined as public in the derived class, or vice-versa!"
A function that is defined as private in base class cannot be accessed by the derived class let alone redefining it.
Hi Alex
Can I redefine a member variable in the derived class?
I'm not sure what you mean by "redefine" in this context. You can't have a derived class change the type of a variable in the base class, but you can declare a member variable with the same name. This member variable will shadow the variable in the base class with the same name.
It is written "Therefore, a function that is defined as private in the base class can redefined as public in the derived class, or vice-versa!"
But the derived class can not access private members/functions of base class . so how is it possible ?
If a function is private in the base class and reimplemented as public in the derived class, the derived version will be callable externally, but it won't be able to access private members from the base class (after all, they are private). There no rule-breaking here.