Overloading has the same scope, same name, different signatures, and virtual
is not required. Overriding has different scopes, same name, same signatures, and virtual
is required.
The term signature designates the combination of a function's name, the types and order of its parameters, and, if the function is a nonstatic member function, its const
and/or volatile
qualifiers.
Overloading occurs when two or more functions are in the same scope (for example, both in the same class or both at namespace scope) and have the same name but different signatures. Overriding occurs when a class and one of its derived classes both define a member function with the same signature and the member function is declared to be virtual in the base class.
In the following example, Base::f(int)
and Base::f(float)
overload each other, while Derived::g()
overrides Base::g()
.
The output of this program follows.
Base::f(int)
Base::f(float)
Derived::g()
A rule in C++ that tends to confuse new C++ developers.
The hiding rule says that an entity in an inner scope hides things with the same name in an outer scope. And since a class is a scope, this means that a member of a derived class hides a member of a base class that has the same name as the derived class member. Confused? Don't give up; this is really important stuff.
There are two common situations when the hiding rule confuses people. First, when a base class and a derived class declare member functions with different signatures but with the same name, then the base class member function is hidden. Second, when a base class declares a nonvirtual member function and a derived class declares a member function with the same signature, then the base class member function is hidden (technically the same thing happens with virtual member functions, but in that case it hardly ever confuses people).
In the following example, Base::f(float)
and Base::g(float)
are virtual and therefore can be overridden by derived classes, but Base::h(float)
is nonvirtual and therefore should not be redefined in derived classes.
In the following code, member function Derived::f(float)
is a normal override of virtual Base::f(float)
. However, Derived::g(int)
hides (rather than overrides or overloads) Base::g(float)
and Derived::h(float)
hides (rather than overrides or overloads) Base::h(float)
.
Because Derived::f(float)
is a normal override of Base::f(float)
, calling f(3.14f)
on a Derived
object does the same thing independent of whether the reference to the Derived
object is of type Base&
or type Derived&
. Said simply, the behavior depends on the type of the object, not on the type of the reference. This is the normal (and desirable) effect of dynamic binding, and it is shown in sampleOne()
.
Unfortunately, Derived::g(int)
neither overrides nor overloads Base:: g(float)
but rather hides Base::g(float)
. Therefore the compiler calls g(float)
if someone tries to call g(int)
on a Derived&
. This behavior is surprising to many developers; it is shown in sampleTwo()
.
Also unfortunately, Derived::h(float)
is a redefinition of the nonvirtual function Base::h(float)
. Since Base::h(float)
is nonvirtual, Derived:: h(float)
is not an override, and dynamic binding does not occur. Therefore, the compiler calls Base::h(float)
if someone tries to call h(float)
on a Derived
object using a Base&
. This behavior is surprising to many developers; it is shown in sampleThree()
.
The root problem with sampleTwo()
and sampleThree()
is that the behavior depends on the type of the reference rather than on the type of the object. For example, in sampleThree()
the member function that gets invoked is the one associated with the reference's type, not the one associated with the object's type. These behaviors surprise users, since users normally expect behavior to depend on the type of the object rather than on the type of the reference or pointer used to access that object.
The hiding rule may not seem intuitive, but it prevents worse errors, especially in the case of assignment operators. If, for example, the hiding rule were removed, it would be legal to assign a Circle
with a Square
(the Shape
part of the Square
would be copied into the Shape
part of the Circle
).
Avoid triggering the hiding rule when possible, and use the following work-arounds when the hiding rule can't be avoided.
Avoid hiding inherited public:
member functions whenever possible. When it cannot be avoided, it is important not to surprise the class's users. The guiding principle is to avoid confusing users: when a Base*
can be used to call a member function on a Derived
object, calling it via a Derived*
shouldn't alter the observable behavior.
In the case of redefining a nonvirtual member function, as in Base::h(float)
from the previous FAQ, the simplest way to avoid surprising users is to use the virtual
keyword when declaring the base class member function. In those rare cases where the base class function cannot be virtual, ensure that the observable behavior of the derived class function is identical to that of the base class.
For example, an experienced C++ programmer might use a nonvirtual member function to avoid the (small) overhead of a virtual function call, yet might also redefine that member function in a derived class to make better use of the derived class's resources. To avoid surprising users, there must not be any differences in the observable behavior of the two functions. Note: These relationships are somewhat subtle; if the code will be maintained by less experienced programmers, a normal, virtual function is probably a better choice.
In the case where a base class and a derived class declare member functions with the same name but different signatures, as in Base::g(float)
and Derived::g(int)
in the previous FAQ, a using declaration (FAQ 29.04) should be employed.
The following shows how these guidelines can be applied to the example from the previous FAQ.
After applying these fixes, users are not confused because the behavior depends on the type of the object rather than on the type of the pointer used to access that object.
The output of this program demonstrates that the behavior depends on the type of the object, not the type of the reference:
Derived::f(float)
Derived::f(float)
Derived::g(float)
Derived::g(float)
Derived::h(float)
Derived::h(float)
These guidelines apply only to public
inheritance; hiding base class member functions is fine for private
or protected
inheritance (see FAQ 37.01).
The derived class should use the using
syntax.
If the base class has an overloaded set of member functions and the derived class overrides some but not all of that set, the redefined member functions will hide the other overloads. The work-around is to use the using
syntax. The following example shows class Base
with two overloaded member functions called f
.
Now suppose the author of class Derived
wants to override one of the two f()
member functions. In this case the derived class should also say using Base::f;
to avoid confusing users:
Because of the using Base::f;
line in the derived class, f(float)
is not hidden:
The output of this program demonstrates that the behavior depends on the type of the object, not the type of the reference:
Derived::f(int)
Derived::f(int)
Base::f(float)
Base::f(float)
This guideline applies only to public
inheritance; hiding base class member functions is fine for private
or protected
inheritance (see FAQ 37.01).
Yes, but it's often easier to use nonvirtual overloads that call nonoverloaded virtuals.
As was discussed in FAQ 29.02, when virtual member functions are overloaded, the hiding rule forces derived classes to do a bit more work than necessary. In these situations, it is often easier if the overloads are nonvirtuals that call virtuals that aren't overloaded. These nonoverloaded virtuals are normally protected:
.
The following code shows how to apply this guideline to the situation in the previous FAQ where Base::f(int)
and Base::f(float)
are overloaded virtuals. These functions are now nonvirtuals that call nonoverloaded virtuals f_i(int)
and f_f(float)
. (Don't redefine nonvirtual member functions; see FAQ 29.02.)
In class Derived
, the behavior of f(int)
is changed by overriding Base::f_i(int)
; redefining f(int)
itself would be wrong, since a redefinition would hide Base::f(float)
.
Now when member function f(int)
is invoked on a Derived
object, it expands inline as the code of Base::f(int)
, which calls protected member function f_i(int)
, and since f_i(int)
is virtual, it resolves to the correct member function using dynamic binding (see FAQ 2.24). The message here is that both f(int)
and f(float)
work correctly on both a Derived&
and on a Base&
:
The output of this program demonstrates that the behavior depends on the type of the object, not the type of the reference:
Derived::f(int)
Derived::f(int)
Base::f(float)
Base::f(float)
This approach is more scalable than the approach presented in the earlier FAQ. It is scalable in two ways, with respect to the code and with respect to the people. With respect to the code, the root of the inheritance hierarchy has a few extra lines of code, but none of the (potentially many) derived classes need have any extra code to handle the hiding rule. This is a good trade-off since an inheritance hierarchy often has many derived classes. With respect to the people, the developer of the root class of the inheritance hierarchy needs to understand the hiding rule, but all the writers of all the derived classes can remain relatively ignorant of it—they need to know only that they are to override the virtual member functions rather than the nonvirtual member functions. This is a good trade-off because the developers who build the root classes in the inheritance hierarchies are normally more sophisticated than the developers who build the derived classes.
Note that this approach does not imply any performance overhead, since the overloaded public:
member functions are normally inline
nonvirtuals.
As before, this guideline applies only to public
inheritance; hiding base class member functions is fine for private
or protected
inheritance (see FAQ 37.01).