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 <iostream> 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"; }
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); }
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). |