C++ / a working model

19 / 163   ·   C++11   ·   9 min

Overload, Override, and Hiding: Overload / Override / Hiding

Keep this sentence

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
  1. Lookup first, then overload resolution
  2. Override matches the virtual-function contract
  3. Default arguments are not part of dynamic dispatch
  4. Example
  5. Exercise

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 && ./example

Expected 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.

Back to the catalog