The following article translated from Ian Joyner "C ?? a critique of C and program" 3 / e [Ian Joyner 1996] The original copyright belongs to Ian Joyner, and I am from the consent of Ian Joyner himself. This article is translated into Chinese. Therefore, the Chinese version of this article should belong to me ;-) The English and Chinese versions of this article are used for non-commercial purposes, you can copy and post it at will. However, it is best to add my declaration when reposing it. If someone or agency wants to publish this article, please contact the original copyright owner and me.
This article has been included in the "Objects Unencapsulate" book written in Ian Joyner (there is already a translation version of Japanese), which is described in: http://www.prenhall.com/AllBooks/ptr_0130142697 .htmlhttp: //efsa.sourceforge.net/cgi-bin/view/main/ObjectsunencapsulatedHttp://www.accu.org/bookreviews/public/reviews/O/O002284.htm
Ian Joyner Contact: I.Joyner@acm.org My Contact: cber@email.com.cn
Preface: [Translator Written] To completely master a language, not only know what it is, but also knows what it is in the shortcomings. This way we can use this language, and we can say that we have mastered this language. Discussion Series in the shortcomings of C (1)
Virtual function
In all criticisms of C , this part of virtual function is the most complicated. This is mainly caused by complex mechanisms in C . Although this article believes that Polymorphism is a key feature of object-oriented programming (OOP), please do not feel anything about this point of view (ie, the virtual function mechanism is a big failure in C ), continue to see Go, if you just want to know a probably, then you can also skip this section. [Translator Note: It is recommended to see if this section will be better]
In C , when the sub-class rewriting / redefine is the function defined in the parent class, the keyword Virtual makes the function have a polymorphism, but the Virtual keyword is not essential. (As long as it is defined in the parent class). The compiler implements a real polymorphic function call by generating dynamic dispensing.
In this way, in C , the problem is generated: if the person who is designing the parent class cannot preview which function can be rewritten, the subclass cannot make this function a polymorphism. This is a very serious defect for C because it reduces the elasticity of Software Components, making it difficult to write reusable and scalable function libraries.
C also allows the overload (OVERLOAD), in which case the compiler makes the correct function call through incoming parameters. The real parameters referenced when the function calls are referenced must anastically consistent with the edge type type of a function in the overloaded function. Different load functions differ from the rewrite function (function of polymorphism) in: The call of the overload function is determined during the compilation period, and the call of the rewrite function is determined during operation. When a parent class is designed, the programmer can only guess the subclass may overload / rewritten which function. Subclasses can overload any functions at any time, but this mechanism is not a polymorphism. In order to achieve polymorphism, the programmer for designing parental class must specify a function to be Virtual, which tells the compiler to establish a Class Jump Table [Translator to be VTABLE, ie virtual function entry table] A distribution entry. So, for deciding what is done automatically by the compiler, or automatically completes this heavy responsibility by the compiler of other languages. These are inherited from the initial C implementation, and some specific compilers and coupons are independent.
For rewriting, we have three different options, respectively, "Don't", "can" and "must" rewrite: 1. Rewinding a function is prohibited. Subclasses must use existing functions 2, and the function can be rewritten. Subclasses can use existing functions, or you can write your own functions, provided that this function must follow the initial interface definition, and the implementation is as small as possible, and the function is an abstract function. There is no implementation for this function, each subclass must provide the designer of its respective implementation of the parent class, must determine the functions in 1 and 3, while the designer of the subclass only needs to consider 2. For these options, the programming must provide direct syntax support. Options 1, C does not disable override a function in the subclass. Even functions that are declared as Private Virtual can also be rewritten. [Sakkinen92] pointed out that even if the Private Virtual function can be accessed by other methods, the subclass can also rewrite it. [Translator Note: SAKKINEN92 I have never seen it, but I have a simple test, I can indeed rewrite the private virtual function in the parent class in the subclass. The only way to implement this choice is not to use virtual functions, but this is If the function is equal to the entire replacement. First, the function may replace it in the unintentional subscriber. Repriving a function in the same scope will result in the name conflict; the compiler will report a "DUPLICATE DECLATION" syntax error. Allows two entities with the same name to exist in the same Scient, ambiguity, and other issues (see DAME OVERLOADING). The following example clarifies the second question: Class a {public: void nonvirt (); Virtual void virt ();}; class b: public a {public: void nonvirt (); void virt ();}; a a ; B; a * ap = & b; b * bp = & b; bp-> nonvirt (); file: // calls b :: Nonvirt as you 10) AP-> nonvirt (); file: // calls a: : Nonvirt Even Though this Object is of type b AP-> Virt (); file: // calls b :: Virt, The Correct version of the routine for b objects In this example, B extends or replaces A function. B :: Nonvirt is a function that should be called by the object of B. Here we must point out that C will call A :: Nonvirt or B :: Nonvirt, but we can also provide a more elasticity to client programmers Simple, more direct way: provide different names for A :: Nonvirt and B :: Nonvirt. This makes the programmer correctly, explicitly modulate the function you want to call, rather than falling into the kind of embarrassment, which is easy to cause the wrong trap.
The specific method is as follows: Class B: public a {public: void b_nonvirt (); void virt ();} b b; b * bp = & b; bp-> nonvirt (); file: // calls a :: nonvirt bp- > b_nonvirt (); file: // Calls B :: B_nonvirt Now, B's designer can directly manipulate B interface. Program Requirements B client (ie, calling B) can call A :: Nonvirt and B :: Nonvirt at the same time, this is also done. For Object-Oriented Design (OOD), this is a good approach because it provides a strong interface definition (the translator believes that no interface of call ambiguity is not). C allows client programmers to sell their skills at the interface of the class, to expand the class. In the previous example, the programmer of design B cannot prevent other programmers from calling A :: NonVirt. The objects of class B have their own nonvirt, but even if so, B's designers cannot guarantee that the interface to the correct version will be called to the correct version of NonVirt. C also does not prevent the changes in the system from other changes from B. Suppose we need to write a class C, which we ask Nonvirt to be a virtual function. So we must go back to A to change Nonvirt to virtual. But this will make our skills to play with B :: Nonvirt, lose their role (think about it, why: d). For C needs a Virtual requirement (changed the existing Nonvirtual to Virtual) makes we changed the parent class, which in turn has changed accordingly from the subclass of the parent class. This has violated the reasons for OOP with low-coupling classes, new needs, and changes should only produce local impact, rather than changing other places in the system, potentially destroying the existing parts of the system. Another problem is that the same statement must have maintained the same semantic. For example, for the interpretation of a polymorphism statement such as A-> F (), the system call is the f () that is most compliant with the true point to the type, regardless of the object of the object is A, or a child class. However, for C programmers, they must clearly understand the true meaning of A-> F () when F () is defined as Virtual or Non-Virtual. Therefore, statement A-> f () cannot be independent of its implementation, and the hidden implementation principle is not constant. A change in the declaration of F () will change the semantics when calling it accordingly. Independence with implementation means that changes in implementation do not change the semantics of the statement, or the semantics of the execution. If the change in the declaration causes a change in semantics, the compiler should detect errors. Programmers should maintain semantics unchanged without declaring changes. This reflects the dynamic characteristics in software development, where you will find the permanent change of program text. Other other examples corresponding to A-> f (), the semantic cannot be kept unchanged is: Construction function (can be refer to C ARM, Section 10.9C, P 232). Eiffel and Java do not have such problems. The mechanisms used in them are simple and clear, which will not cause those surprising phenomena from C . In Java, all together is virtual, in order to let a method [translator note: a function corresponding to C can not be rewritten, we can use Final modifiers to modify this method.
Eiffel allows programmers to specify a function of Frozen, in which case this function cannot be rewritten in the subclass. Option 2 is whether to use an existing function or rewrite one, which should be determined by the programmer written by the subclass. In C , if you want to have this ability, you must specify as Virtual in the parent class. For OOD, what you decide to be as important as you decide, you should be, the better it is, the better it. This strategy avoids errors that are included in the system. The sooner you decide, the more likely you have proved to be in the wrong assumption; or the assumption you make in one case is correct, but in another case, it will be wrong, thus The software you have written is relatively fragile. Reusable [Translator Note: The reusability of the software is a very important feature for software, specifically refer to "Object-Oriented Software Construction" for software Description of external characteristics, P7, Reusability, Charpter 1.2 A Review of External Factors. C requires us to specify possible polymorphism in the parent class (this can be specified by virtual), of course, we can also import the Virtual mechanism in the middle of the inheritance chain, so that a function can be in subcan in advance. The class is redefined. This approach will lead to problems: such as those that are not actually polymorphic, must also be called through the low efficiency Table technology, and unlike the function of directly calling that function [Translator Note: In the context of the article, there is no exact definition of NOT Actually Polymorphic feature. According to my understanding, it should be declared as PolyMorphic, and the actual action does not reflect a feature such as PolyMorphic]. Although this does not cause a lot of cost, but we know that in the OO program, there will often be a large number of short, target single and clear functions. If all of these are accumulated, it will lead to one A considerable cost of spending. The policy in C is this: The function that needs to be redefined must be declared as Virtual. Worse, C also said, the Non-Virtual function cannot be redefined, which makes the programmer who design the subclass of the program to have its own control for these functions. [Translator Note: The original sentence seems to be scrutinized, the original is written this: it Says That Non-Virtual Routines Cannot Be Redefined, I speculate that the author wants to express, it should be: if you have defined A Non-Virtual Routine in Base, THEN ITHER YOUNDEFINED IT AS Virtual in Descendant.】 Rumbauh et al. The criticism of virtual mechanisms in C is as follows: C has a simple implementation of inheritance and dynamic method calls, but a C The data structure does not automatically become object-oriented. Method Call Resolution and the premise of rewriting a function operation in the subclass must be that this function / method has been declared in the parent class as Virtual. That is, we must foresee if a function needs to be rewritten in the initial class.
Unfortunately, the writer of the class may not expect to define a special subclass, or may not know that those operations will be rewritten in the subclass. This means that when the subclass is defined, we often need to go back to modify our parent class, and make it extremely strict for reuse the existing libraries by creating a subclass, especially when this library is not This is even more. (Of course, you can also define all the operations as Virtual, and willing to pay some small memory costs for this.) [RBPEL91] However, let the programmer to process Virtual is an error mechanism. The compiler should be able to detect the polymorphism and generate the code for this, potential implementation of Virtual. Let the programmer to determine whether the Virtual is a burden on the programmer for programmers. This is why C can only be a weak object-oriented language: because the programmer must always pay attention to some bottom detail, and these can be automatically processed by the compiler. . Another problem in C is the wrong rewriting, and the function in the parent class can be rewritten without knowing. The compiler should report an error in the same namespace, unless the programmer written by the subclass pointed out that he is interested in doing this (ie, for the virtual function overwriting). We can use the same name, but programmers must know what they are doing and explicitly declare it, especially in the case of assembling their own procedures and existing program components to become new systems. Unless the programmer explicitly rewrites existing virtual functions, the compiler must give us an error that appears many Duplicate Declarations. However, C uses Simula initial practices, and this approach has been improved. Other programming languages have avoided erroneous redefinitions by using a better, more explicit approach. The solution is that Virtual should not be specified in the parent class. When we need to run dynamic binding, we specify a function to rewrite a function in the subclass. The benefit of this is that for functions with polymorphism, the compiler can detect the consistency of Function Signature; for overloaded functions, its function signature is not the same as some respects. The second benefit is manifested, in the maintenance phase of the program, can clearly express the initial will of the program. In fact, the later programmers often have to guess what the previous programmers have made mistakes, choose an identical name, or he wants to overload this function. In Java, there is no Virtual keyword, all methods are polymorphic at the bottom. When the method is defined as static, private, or final, Java directly calls them instead of by dynamic check tables. This means that when they need to be called, they are non-polymorphic functions, this dynamic characteristics of Java make the compiler difficult to further optimize. Eiffel and Object Pascal catere this option. In they, programmers who write subclasses must specify the redefine action they want. We can get a huge benefit from this practice: For those who will read those who will read these programs and procedures, it can be easily found to be rewritten. Thus the option 2 is preferably implemented in the subclass. Both Eiffel and Object Pascal optimize function calls: because they only need to generate the entry items of the invocation table of true polymorphisms. For how to do, we will discuss in the Global Analysis section.