19 / 163 · C++11 · 9 min
Overload, Override, and Hiding: Overload / Override / Hiding
Overload selects a signature from candidate functions at compile time; override decides which implementation a virtual call ultimately executes; hiding happens at the name-lookup stage. All three can appear at once. Use override to check override intent, and use using to restore the needed set of base-class overloads.
In this lesson
Lookup first, then overload resolution
Overloading means one name corresponds to several candidate functions; the compiler chooses the best match according to argument types, conversions, constraints, and related rules. Ordinary function overloads cannot be distinguished by return type alone. As soon as a derived class declares a member with the same name, it may hide the entire same-name set from the base class in its scope, even if the parameter types are completely different.
Therefore Derived::f(double) does not automatically form the hoped-for overload set with Base::f(int). When you need to keep the base-class candidates, add using Base::f; in the derived class. That affects name lookup; it does not turn a non-virtual base-class function into a virtual function, nor does it magically create an override relationship.
Override matches the virtual-function contract
Override requires the base-class function to be virtual and to satisfy corresponding requirements on the parameter list, member cv-qualifiers, ref-qualifiers, and so on. The return type must be the same or satisfy the covariance rules, and the exception specification must not relax a non-throwing promise of the base. Even if the derived class omits virtual, a function that successfully overrides remains virtual.
Write override whenever you intend to override, so that omitting const or getting a parameter wrong becomes a compile error. Access rights do not participate in judging an override: a private virtual function can still be overridden. Covariance applies only to qualifying class pointers or references; you cannot override by changing a unique_ptr<Base> return type to unique_ptr<Derived>.
Default arguments are not part of dynamic dispatch
When a virtual function is called, overload resolution and default arguments are taken from the static context of the call expression; then the virtual mechanism selects the final overrider. In the example, calling value() through Base& picks up the default of one but executes Derived's implementation; calling through the derived object picks up the default of two.
Writing d.Base::value() explicitly suppresses virtual dispatch for that call and invokes the base-class implementation directly. When designing an interface, it is best not to change default arguments on overriding functions; otherwise the same object behaves differently depending on the reference type through which it is observed, which is easy to misread.
Pitfalls
- Omitting const may declare a new function and hide a name rather than override; override exposes this kind of error early.
- using introduces a base-class function; it does not reimplement it. Ordinary calls to a virtual function still follow the final-overrider rule.
Run an example
Minimum C++11 · complete program · Download .cpp
#include <cassert>
struct Base {
int convert(int) const { return 1; }
virtual int value(int n = 1) const { return n; }
virtual ~Base() = default;
};
struct Derived : Base {
using Base::convert;
int convert(double) const { return 2; }
int value(int n = 2) const override { return n * 10; }
};
int main() {
Derived d;
const Base& b = d;
assert(d.convert(3) == 1);
assert(d.convert(3.5) == 2);
assert(b.value() == 10);
assert(d.value() == 20);
assert(d.Base::value() == 1);
}
Compile locally
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread objects-overload-override-hide.cpp -o example && ./exampleExpected result
Expected: exit 0, no output; every assert holds.
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
After only deleting using Base::convert;, what is the result of d.convert(3), and why?
Show a reference answer
The result becomes two. The derived class's same-name declaration hides the base convert, so name lookup finds only Derived::convert(double); the integer three can convert to double, so the call is still well-formed. The compiler will not skip the hiding relationship to choose the originally more exact Base::convert(int). Restore using, or call d.Base::convert(3) explicitly, to select the base-class version.
Check the sources
Drafts and official chapters change. The version mark is only the example’s minimum.