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



C++ FAQs
C Programming FAQs: Frequently Asked Questions
ISBN: 0201845199
EAN: 2147483647
Year: 2005
Pages: 566
Authors: Steve Summit

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net