Wednesday, August 29, 2007

Mixing Overloading with Inheritance


What is the difference between overloaded functions and overridden functions?

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().

#include 
using namespace std;
class Base {
public:
  virtual ~Base()         throw();
  virtual void f(int x)   throw();
  virtual void f(float x) throw();
  virtual void g()        throw();
};
 
Base::~Base() throw()
  { }
void Base::f(int x) throw()
  { cout << "Base::f(int)\n"; }
void Base::f(float x) throw()
  { cout << "Base::f(float)\n"; }
void Base::g() throw()
  { cout << "Base::g()\n"; }
 
class Derived : public Base {
public:
  virtual void g() throw();
};
 
void Derived::g() throw()
  { cout << "Derived::g()\n"; }
 
int main()
{
  Derived d;
  Base* bp = &d;  // OK: Derived is kind-of Base
  bp->f(42);
  bp->f(3.14f);
  bp->g();
}

The output of this program follows.

Base::f(int)
Base::f(float)
Derived::g()

FAQ 29.02 What is the hiding rule?

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.

#include 
using namespace std;
 
class Base {
public:
  virtual ~Base()         throw();
  virtual void f(float x) throw();
  virtual void g(float x) throw();
          void h(float x) throw();
};
 
Base::~Base() throw()
  { }
void Base::f(float x) throw()
  { cout << "Base::f(float)\n"; }
void Base::g(float x) throw()
  { cout << "Base::g(float)\n"; }
void Base::h(float x) throw()
  { cout << "Base::h(float)\n"; }

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

class Derived : public Base {
public:
  virtual void f(float x) throw();                   <-- 1
  virtual void g(int x)   throw();                   <-- 2
          void h(float x) throw();                   <-- 3
};
void Derived::f(float x) throw()
  { cout << "Derived::f(float)\n"; }
void Derived::g(int x) throw()
  { cout << "Derived::g(int)\n"; }
void Derived::h(float x) throw()
  { cout << "Derived::h(float)\n"; }

(1) Good: Overrides Base::f(float)

(2) Bad: Hides Base::g(float)

(3) Bad: Redefines a nonvirtual function

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().

void sampleOne(Base& b, Derived& d)
{
  b.f(3.14f);                                        <-- 1
  d.f(3.14f);
}
 
int main()
{
  Derived d;
  sampleOne(d, d);
}

(1) Good: If the object is a Derived, calls Derived::f(float)

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().

void sampleTwo(Base& b, Derived& d)
{
  b.g(3.14f);
  d.g(3.14f);                                        <-- 1
}
 
int main()
{
  Derived d;
  sampleTwo(d, d);
}

(1) Bad: Converts 3.14 to 3 and calls Derived::g(int)

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().

void sampleThree(Base& b, Derived& d)
{
  b.h(3.14f);                                        <-- 1
  d.h(3.14f);
}
 
int main()
{
  Derived d;
  sampleThree(d, d);
}

(1) Bad: Calls Base::h(float)—does not use dynamic binding

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

FAQ 29.03 How should the hiding rule be handled?

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.

#include 
using namespace std;
 
class Base {
public:
  virtual ~Base()         throw();
  virtual void f(float x) throw();
  virtual void g(float x) throw();
  virtual void h(float x) throw();
};
 
Base::~Base() throw()
  { }
void Base::f(float x) throw()
  { cout << "Base::f(float)\n"; }
void Base::g(float x) throw()
  { cout << "Base::g(float)\n"; }
void Base::h(float x) throw()
  { cout << "Base::h(float)\n"; }
 
class Derived : public Base {
public:
  virtual void f(float x) throw();
  virtual void g(int x)   throw();                   <-- 1
  using Base::g;                                     <-- 2
};
 
void Derived::f(float x) throw()
  { cout << "Derived::f(float)\n"; }
void Derived::g(int x) throw()
  { cout << "Derived::g(int)\n"; }

(1) Normally this would hide g(float) (bad!)

(2) But this line unhides g(float) (good!)

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.

void sample(Base& b, Derived& d)
{
  b.f(3.14f);
  d.f(3.14f);
 
  b.g(3.14f);
  d.g(3.14f);                                        <-- 1
 
  b.h(3.14f);
  d.h(3.14f);
}
 
int main()
{
  Derived d;
  sample(d, d);
}

(1) This is not hidden (good!)

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

FAQ 29.04 What should a derived class do when it redefines some but not all of a set of overloaded member functions inherited from the base class?

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.

#include 
using namespace std;
 
class Base {
public:
  virtual ~Base()         throw();
  virtual void f(int x)   throw();
  virtual void f(float x) throw();
};
 
Base::~Base() throw()
{ }
 
void Base::f(int x) throw()
{ cout << "Base::f(int)\n"; }
 
void Base::f(float x) throw()
{ cout << "Base::f(float)\n"; }

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:

class Derived : public Base {
public:
  virtual void f(int x) throw();
  using Base::f;                                     <-- 1
};
 
void Derived::f(int x) throw()
{ cout << "Derived::f(int)\n"; }

(1) This unhides f(float) (good!)

Because of the using Base::f; line in the derived class, f(float) is not hidden:

void sample(Base& b, Derived& d)
{
  b.f(42);
  d.f(42);
  b.f(3.14f);
  d.f(3.14f);                                        <-- 1
}
int main()
{
  Derived d;
  sample(d, d);
}

(1) This is not hidden (good!)

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.

FAQ 29.05 Can virtual functions be overloaded?

Yes, but it's often easier to use nonvirtual overloads that call nonoverloaded virtuals.

, 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.)

#include 
using namespace std;
 
class Base {
public:
  virtual ~Base() throw();
  void f(int x)   throw();
  void f(float x) throw();
protected:
  virtual void f_i(int x)   throw();
  virtual void f_f(float x) throw();
};
inline void Base::f(int x) throw()                   <-- 1
  { f_i(x); }
inline void Base::f(float x) throw()
  { f_f(x); }
void Base::f_i(int x) throw()                        <-- 2
  { cout << "Base::f(int)\n"; }
void Base::f_f(float x) throw()
  { cout << "Base::f(float)\n"; }
 
Base::~Base() throw()
  { }

(1) Overloaded nonvirtuals

(2) Nonoverloaded virtuals

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

class Derived : public Base {
public:
                                                     <-- 1
protected:
  virtual void f_i(int x) throw();                   <-- 2
};
 
void Derived::f_i(int x) throw()
  { cout << "Derived::f(int)\n"; }

(1) Derived classes never redefine f(int)

(2) Derived classes may override f_i(int)

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&:

void sample(Base& b, Derived& d)
{
  b.f(42);
  d.f(42);
 
  b.f(3.14f);
  d.f(3.14f);                                        <-- 1
}
 
int main()
{
  Derived d;
  sample(d, d);
}

(1) This is not hidden (good!)

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 .

0 comments;Click here for request info on this topic:

Post a Comment