ArticleDirectory
- I. Preface
- 2. Introduction: overload, override, and hide)
- Iii. Function inheritance rules:
- Iv. Postscript
- V. References
[Understanding C ++ (2)] understanding interface inheritance rules
Luo chaohui (http://www.cnblogs.com/kesalin/)
CC license. For more information, see Source I. Preface
In the previous article [understanding C ++ (I)] type conversion (Type Casting), I gave a detailed description of the transformation actions in C ++ and usage rules. Some netizens said they should mention the content in the book "Deep Exploration of the C ++ object model". In fact, they mean that if they do not know much about the memory layout of C ++ objects, I want to thoroughly understand the type transformation, object cutting, and virtual function calling in C ++, as if I want to build an air loft without a solid foundation. Understanding the memory layout of C ++ objects is crucial to learning C ++, but I do not plan to write articles related to the memory layout of C ++ objects, because we have to stand on the shoulders of our predecessors, Daniel Chen Hao has already written three illustrated articles on this topic:
(1), c ++ virtual function table Parsing
(2) memory layout of C ++ objects (on)
(3) memory layout of C ++ objects (bottom)
Before continuing to read this article, we recommend that you read these three articles to better understand this series of articles. In the following content, I will start with the concepts of overloading, rewriting, and blocking and introduce many interface inheritance rules.
2. Introduction: overload, override, and hide)
Overload): In the same scope, functions with the same name and different parameters or constants are called overload. The distinction between overloaded functions lies mainly in the difference between parameters and Const. If only the return value or modifier is virtual, different public/protected/private values are not considered as overload functions (compilation fails ). Different parameters refer to different numbers or types of parameters. Different types refer to user-defined type conversion that cannot be performed between different types or not more than once (about type conversion, see the previous article: type casting )). When a call occurs, the compiler selects the best matching function based on the parameters provided by the call when making a heavy-load resolution.
Override): The derived class overrides the functions with the same name and parameters in the base class that return the same value (usually virtual functions, which is recommended ). Similarly, rewrite functions can have different modifiers: Virtual, public, protected, and private.
Shield (hide): A function with the same name but different parameters or const is provided in an internal scope (derived class, nested class or namespace, this makes the function with the same name in the peripheral scope invisible in the internal scope. When the compiler searches for the name, it finds the name in the internal scope to stop searching for the peripheral scope, thus shielding the function with the same name in the peripheral scope.
(Note: when determining which function should be called, the compiler should perform three tasks in sequence: Name Search, reload resolution, and access check. The decision process will be detailed in subsequent articles .)
The following is an example:
Class Base { Public : Virtual Void F () {cout < " Base: F () " < Endl ;} Void F ( Int ) {Cout < " Base: F (INT) " < Endl ;} Virtual Void F ( Int ) Const {Cout < " Base: F (INT) const " < Endl ;} Virtual Void F ( Int *) {Cout < " Base: F (int *) " < Endl ;}}; Class Derived:Public Base { Public : Virtual Void F () {cout < " Derived: F () " < Endl ;} Virtual Void F ( Char ) {Cout < " Derived: F (char) " < Endl ;}}; Const Base B; B. F ( 10 ); Derived D; Int Value = 10 ; D. F (); D. F ( ' A ' ); D. F ( 10 ); // D. F (& value ); // Compilation Error
AboveCodeIn base, a series of functions named F are in the same scope and have different parameters or constants of the same name.Heavy LoadFunction, while F () in derived isRewriteWhile F (char) in derived isShieldAll functions with the same name in the base.
Therefore, the execution result of the above Code is:
Base: F (INT) const
Derived: F ()
Derived: F (char)
Derived: F (char)
To D. F (10); these two calls seem to have a better match in the base class, but in fact, because the compiler first searches for the name in the derived class scope and finds F (CharSo all functions with the same name of the base class have no chance to go to the overload resolution.Shield. Therefore, the compiler implicitly converts 10 to Char and calls F (Char). So far, you should be smart and easily understand whyD. F (& value );It cannot be compiled. (The prompt message of vs compiler is awesome ).
Iii. Function inheritance rules:
In view of the obscure concepts of the inherited base class functions, we need to understand them. In addition, we need to add virtual functions, Public/protected/private inheritance, and so on, it makes it more difficult to understand a class interface (because you should not only look at the class's own interface, but also trace back all the base class interfaces, and how to inherit the base class interfaces ). Therefore, C ++ has many common usage for class interface inheritance:
1,Combination rather than inheritance is preferred.Since the inheritance cost is so high, the best thing is not to inherit. Of course not to say that there is no need to inherit, only in the existence of a clear "IS-A" relationship, the benefits of inheritance will appear (can use polymorphism-but follow the liskov replacement principle ); in other cases ("HAS-A" or "is-implemented-in-terms-of"), you should not hesitate to use the combination, and use pimpl (point to implementation) first) method (this will be introduced in later articles) to use combinations.
2,Pure virtual function inheritance rules-The purpose of declaring a pure virtual function is to let the derived class inherit the function interface rather than implement it, so that the pure virtual function is like the interface in Java or C. The only exception is the need for pure destructor (to avoid resource leakage ).
3,Non-pure virtual function inheritance rules-The purpose of declaring a non-pure virtual function is to let the derived class inherit the function interface and default implementation. However, this is a poor practice, because the default implementation can allow newly added derived classes without rewriting this implementation to compile and run, and the default implementation may not be suitable for newly added derived classes, the compiler does not provide any information (either warning or warning ). To cope with this potential trap, another rule emerged:"The Declaration of pure virtual functions provides interfaces, while the implementation of pure virtual functions provides default implementation. The derived class must override this interface, but the default Implementation of the base class can be called during implementation. "
The following code is used:
Class Base { Public : Virtual Void F () = 0 ;}; Void Base: F () {cout < " Base: F () default implement. " < Endl ;} Class Deriveda: Public Base { Public : Virtual Void F () {base: F ();}}; Class Derivedb:Public Base { Public : Virtual Void F () {cout < " Derivedb: F () override. " < Endl ;}};
4,Non-virtual function inheritance rules-Never override non-virtual functions in the base class. The purpose of a non-virtual function is to make the derived class inherit the mandatory implementation of the base class, and it does not want to be rewritten by the derived class.
5,Do not mask the name of the peripheral scope (including inherited ones.The problem of concealment and difficulty in understanding caused by blocking is described above.
If you do not have a choice (I did not expect this to happen in some scenarios, it is usually feasible to change the name), you must redefine or override functions with the same name in the base class, then you should introduce a using declaration or use a transfer function for each hidden name (the derived class defines the same name as the Parameter Function and calls the same name as the Parameter Function of the base class within the function) to make these names visible in the scope of the derived class. (Valid tive C ++ clause 33 ).
The rule is applied as follows:
Class Base { Public : Virtual Void F () {cout < " Base: F () " < Endl ;} Void F ( Int ) {Cout <" Base: F (INT) " < Endl ;} Virtual Void F ( Int ) Const {Cout < " Base: F (INT) const " < Endl ;} Virtual Void F ( Int *) {Cout < " Base: F (int *) " < Endl ;}}; Class Derived: Public Base { Public : UsingBase: F; Virtual Void F () {cout <" Derived: F () " < Endl ;} // Virtual void F (char) {cout <"derived: F (char)" <Endl ;} }; Const Base B; B. F ( 10 ); Derived D; Int Value = 10 ; D. F (); D. F ( ' A ' ); D. F ( 10 ); D. F ( & Value );
The running result is:
Base: F (INT) const
Derived: F ()
Base: F (INT)
Base: F (INT)
Base: F (int *)
HereUsingBase: F;, so all the names F in the base class are visible to the subclass, and all D. F (&Value); And so on can be compiled and run. Note: This is a very bad practice.
6,The destructor of the base class should be virtual functions to avoid resource leakage.
Assume that the base class pointer Pb with non-virtual destructor points to the object D of a derived class, and the derived class releases some resources in its destructor, if we delete Pb; the destructor of the derived class object will not be called, resulting in resource leakage. Therefore, the destructor of the base class should be declared as virtual functions.
7,Avoid Private inheritance-Private inheritance usually means implementing (is-implemented-in-terms-of) According to something. In this case, it is not appropriate to use a term such as a base class and a derived class, because it does not meet the liskov replacement principle, and all interfaces inherited from the base class are private, external access is not allowed. Private inheritance can be replaced by pimpl.
The pimpl tool has been mentioned twice in this article. Here we will give an example of private inheritance, and then detail the benefits of pimpl.
Original private inheritance:
ClassSomeclass {Public:VoidDosomething (){}};ClassOtherclass:PrivateSomeclass {Private:VoidDosomething (){}};
Use pimpl instead:
Class Someclass { Public : Void Dosomething (){}}; Class Otherclass { Public : Otherclass (); ~ Otherclass (); Void Dosomething (); Private : Someclass *Pimpl ;}; otherclass: otherclass () {pimpl = New Someclass ();} otherclass :: ~ Otherclass () {Delete pimpl ;} Void Otherclass: dosomething () {pimpl -> Dosomething ();}
8,Do not rewrite the inherited default parameter values.We have already mentioned that non-virtual function inheritance is a bad practice, so the focus here is to inherit a virtual function with the default parameter value. Why is the default parameter value inherited from rewriting not good? Because Virtual functions are dynamically bound, but the default parameter value is statically bound, when you perform a multi-state call, the function is determined by the dynamic type, the default parameter is determined by the static type, which violates intuition.
Code has the truth:
Class Base { Public : // In the previous example, to simplify the code without following the virtual destructor rules Virtual ~ Base (){}; Virtual Void F ( Int Defaultvalue = 10 ) {Cout < " Base: F () value = " <Defaultvalue < Endl ;}}; Class Derived: Public Base { Public : Virtual Void F ( Int Defaultvalue = 20 ) {Cout < " Derived: F () value = " <Defaultvalue < Endl ;}};
The output of this Code is:
Derived: F () value = 10
The function interface of the dynamic type D-derived class derived is called, but the default parameter value is determined by the function interface of the static type Pb-base class base, this concealed details may waste an afternoon debugging, so it is better to prevent it early.
9. There is also a genre thatPublic APIs except virtual destructor should not be made publicInstead, a non-virtual function should be published, and the protected/private virtual function should be provided in the non-virtual function. In this wayWhen an interface is called(Non-virtual functions) andHow interfaces are implemented(Virtual functions) are separated to achieve better isolation. In the design mode, this is a strategy mode. Generally, a non-virtual function can be called inline (this can be achieved directly in the header file function declaration). Therefore, the efficiency is comparable to that of calling a virtual function directly.
For example:
Class Base { Public : Virtual ~ Base (){} Void Dosomething () {stepone (); steptwo ();} Private : Virtual Void Stepone () = 0 ; Virtual Void Steptwo () = 0 ;}; Class Derived: Public Base { Private : Virtual Void Stepone () {cout < " Derived stepone: Do something. " < Endl ;} Virtual Void Steptwo () {cout < " Derived steptwo: Do something. " < Endl ;}};
Iv. Postscript
C ++ has many traps. It is not easy to learn and use C ++ well. However, you only need to keep the OO design principles in mind and learn more about C ++'s common practices, the power of C ++ can be well presented.
V. References
Valid C ++ terms 32 ~ 39
More effective tive C ++ terms 20 ~ 25