Objective C ++ Item 33 avoid hiding inherited names.
1. Hide in common scopes
The name actually has nothing to do with inheritance. Scope is related. We all know the following code:
1 int x; // global variable 2 3 void someFunc() 4 { 5 double x; // local variable 6 7 std::cin >> x; // read a new value for local x 8 9 10 }
The declaration of reading x points to the local x instead of the global x, because the name of the internal scope hides the variable of the external scope. We can visualize the scope in the following ways:
When the compiler is in the scope of someFunc, it encounters the name x, which first queries the local scope to see if there is something named after this name. Because they exist, they no longer check for other scopes. In this case, the x of someFunc is of the double type, and the x of global is of the int type. The C ++ name hiding rule will process it: Hide the name. It does not matter whether it corresponds to the same name or different types. In this case, the double named x hides the int named x.
2. How does the compiler find a name in the inheritance system?
Now proceed to inheritance. We know that when we are inside a member function of a derived class and reference some basic classes (for example, a member function, a typedef or a data member ), the compiler can find what we reference, because the derived class inherits those declared in the base class. The actual working method is that the scope of the derived class is embedded in the scope of the base class. For example:
In this class, there are both public and private names, both data members and member functions. Member functions include pure virtual functions, common virtual functions (non-pure virtual functions), and non-virtual functions. This is to emphasize that we want to discuss the name rather than anything else. The example can also contain type names, such as enumeration, nested classes, and typedefs. In this discussion, we only care about their names. It doesn't matter what type they are. This example uses single inheritance, but once you understand what will happen under single inheritance, it is easy to predict the C ++ behavior under multi-inheritance.
Assume that mf4 in a derived class is defined as follows:
1 void Derived::mf4()2 {3 ...4 mf2();5 ...6 }
When the compiler sees that mf2 is used here, they must understand what mf2 points. They will look for a declaration named mf2 in the scope. First, they looked for it in the local scope, but found that there was no declaration named mf4. Next we will look for the include scope, that is, the class Derived. They still do not find mf2, so they enter the next include scope, that is, the base class. The mf2 statement is found here, and the search is complete. If no mf2 exists in the base class, the search continues. First, search for it in the namespace (s) containing Derived, and finally find it in the global scope.
The process we just described is accurate, but it is not comprehensive about how the name is found in C ++. Our goal is not to understand enough names to find rules and then implement a compiler. However, we should understand enough rules to avoid unexpected things. For this job, we have learned a lot of information.
3. How is the name in the inheritance system hidden?
Considering the previous example, in addition to reloading mf1 and mf3, we also added an mf3 version to Derived. (As explained in Item36, The mf3 declaration in Derived-an inherited non-virtual function-makes the design look suspicious, but to understand the visibility of inherited names, we ignore this issue .)
1 class Base { 2 private: 3 int x; 4 public: 5 virtual void mf1() = 0; 6 virtual void mf1(int); 7 virtual void mf2(); 8 void mf3(); 9 void mf3(double);10 ...11 };12 class Derived: public Base {13 public:14 virtual void mf1();15 void mf3();16 void mf4();17 ...18 };
The Behavior Produced by this Code surprised every C ++ programmer who first encountered this situation. The name hiding rule based on the scope does not change. Therefore, all functions named mf1 and mf3 in the base class are hidden by functions with the same name in the derived class. From the perspective of name search, Base: mf1 and Base: mf3 are no longer inherited by Derived!
1 Derived d; 2 int x; 3 ... 4 d.mf1(); // fine, calls Derived::mf1 5 d.mf1(x); // error! Derived::mf1 hides Base::mf1 6 7 d.mf2(); // fine, calls Base::mf2 8 9 d.mf3(); // fine, calls Derived::mf310 11 d.mf3(x); // error! Derived::mf3 hides Base::mf3
As you can see, for a base class with the same name and a function in a derived class, the preceding hidden rule applies even if the parameter type is different, and it has nothing to do with the virtual and non-virtual functions. In the same way at the beginning of this clause, the double x function in someFunc hides the int x in the global scope. Here, the function mf3 in Derived hides the function named mf3 in the base class, even if the parameter types are different.
4. How to overwrite hidden Behaviors
The basic principle behind this behavior is that when you create a new derived class in a library or application framework, it can prevent you from suddenly inheriting overload functions from a distant base class. Unfortunately, you always want to inherit the overload function. In fact, if you are using public inheritance without inheriting overload functions, you violate the "is-a" relationship between the base class and the derived class, this is the basic principle of public inheritance introduced in Item 32. This is the basic situation. You always want to overwrite the default hidden behaviors of inherited names.
4.1 Method 1 use using Declaration
Use the using Declaration to achieve this goal:
The inheritance now works as expected:
1 Derived d; 2 int x; 3 ... 4 d.mf1(); // still fine, still calls Derived::mf1 5 d.mf1(x); // now okay, calls Base::mf1 6 7 d.mf2(); // still fine, still calls Base::mf2 8 9 d.mf3(); // fine, calls Derived::mf310 11 12 13 d.mf3(x); // now okay, calls Base::mf3 (The int x is14 // implicitly converted to a double so that15 // the call to Base::mf3 is valid.)
This means that if your class inherits the base class containing the overloaded function, you want to redefine or override some of the functions, you need to include a using Declaration for each name to be hidden. If you do not do this, some names you want to inherit will be hidden.
4.2 method 2 use the forwarding function
Sometimes it is possible that you do not want to inherit all functions of the base class. In the case of public inheritance, you should always reject this behavior because it violates the "is-a" relation inherited by the public between the base class and the derived class. (This is why the above using declaration is placed in the public part of the derived class: The public name in the base class should also be public in the derived class inherited by public ). However, in private inheritance (see Item 39), it also makes sense. For example, assume that the Derived private inherits from the Base class, and the only version of the Derived class that inherits the Base class function mf1 is a version without parameters. The Using declaration does not work here, because a using declaration makes the names of all inherited functions visible in the derived class. Different technologies can be used here, that is, simple forwarding functions:
1 class Base { 2 public: 3 virtual void mf1() = 0; 4 virtual void mf1(int); 5 6 ... 7 8 // as before 9 10 11 };12 class Derived: private Base {13 public:14 virtual void mf1() // forwarding function; implicitly15 { Base::mf1(); } // inline — see Item 30. (For info16 ... // on calling a pure virtual17 }; // function, see Item 34.)18 ...19 Derived d;20 int x;21 d.mf1(); // fine, calls Derived::mf122 d.mf1(x); // error! Base::mf1() is hidden
Another way to use the inline forwarding function is to use an older compiler. They do not support using the using declaration to import inherited names to the scope of the derived class.
This is all about inheritance and name hiding. But when inheritance is combined with the template, a completely different "inherited name is hidden" problem will arise, for details, see Item 43.
5. Summary
- The names in the derived class are hidden from the base class. In public inheritance, this is by no means satisfactory.
- To repeat the hidden names, use the using declaration or forwarding function.