C ++ deficiency discussion series (1)

Source: Internet
Author: User

 

Virtual Functions

Among all the criticisms of C ++, virtual functions are the most complex. This is mainly caused by the complicated mechanism in C ++. Although this article argues that polymorphism (polymorphism) is a key feature for implementing Object-Oriented Programming (OOP), do not consider it (that is, the virtual function mechanism is a major failure in C ++) if you just want to know a rough idea, you can skip this section. [Note]

In C ++, when the subclass override/redefine defines the function in the parent class, the keyword virtual makes the function have polymorphism, however, virtual keywords are not essential (as long as they are defined once in the parent class ). The compiler calls a real multi-state function by generating a dynamic distribution (Dynamic dispatch.

In this way, in C ++, the problem arises: if the person who designs the parent class cannot predict which function the subclass may rewrite, then the subclass cannot make the function have polymorphism. This is a serious defect for C ++ because it reduces the flexibility of software components ), this makes it difficult to write reusable and scalable function libraries.

C ++ also allows function overloading. In this case, the compiler calls the function correctly by passing in parameters. When calling a function, the referenced real parameter type must be consistent with the shape parameter type of a function in the overloaded function group (overloaded functions. The difference between an overloaded function and an overwritable function (a function with polymorphism) is that the call of an overloaded function is determined during compilation, the calling of the rewrite function is determined during the running period.

When a parent class is designed, the programmer can only guess which function the subclass may overload/override. Subclass can reload any function at any time, but this mechanism is not polymorphism. To achieve polymorphism, the programmer who designs the parent class must specify a function as virtual, which will tell the compiler to jump to the table (class jump table) of the class, create a distribution entry in the virtual function entry table. Therefore, the task of deciding what is automatically completed by the compiler or automatically completed by the compiler in other languages is placed on the shoulders of programmers. These are inherited from the initial implementation of C ++, and have nothing to do with some specific compilers and connectors.

For rewriting, we have three different options, corresponding to: "never", "yes", and "must" rewrite:
1. rewriting a function is forbidden. Subclass must use existing functions
2. functions can be rewritten. Sub-classes can use existing functions or self-written functions, provided that the function must follow the original Interface Definition and implement as few and complete functions as possible.
3. A function is an abstract function. This function does not provide any implementation. each subclass must provide its own implementation.
 
The designer of the parent class must determine the functions in 1 and 3, while the designer of the Child class only needs to consider 2. For these options, the program language must provide direct syntax support.
 
Option 1,
 
C ++ cannot rewrite a function in a subclass. Functions declared as private virtual can be overwritten. Sakkinen92 indicates that even if the private virtual function cannot be accessed through other methods, the subclass can rewrite the function. Note: I have never seen sakkinen92, but after a simple test, I can indeed rewrite the private virtual function in the parent class in the subclass]
 
The only way to achieve this is to avoid using virtual functions, but in this case, the function is replaced. First, functions may be replaced by functions of the quilt class. Declaring a function in the same scope will result in name clash. the compiler will report a "Duplicate Declaration" syntax error. Allowing two entities with the same name to exist in the same scope will cause semantic ambiguity and other problems (see name overloading ).
 
The following example illustrates the second problem:
Class
{
Public:
Void nonvirt ();
Virtual void virt ();
};
Class B: public
{
Public:
Void nonvirt ();
Void virt ();
};
 
A;
B;
A * ap = & B;
B * BP = & B;
 
BP-> nonvirt (); file: // callb: nonvirt as you wowould perform CT
AP-> nonvirt (); file: // calla: nonvirt even though this object is of type B
AP-> virt (); file: // callb: virt, the correct version of the routine for B objects
 
In this example, B extends or replaces the function in. B: nonvirt is a function that should be called by B's object. Here, we must note that C ++ is flexible enough to call a: nonvirt or B: nonvirt for client programmers (that is, programmers who use our inherited system architecture, but we can also provide a simpler and more direct method: provide different names to a: nonvirt and B: nonvirt. This allows programmers to call the functions they want to call correctly and explicitly, instead of getting stuck in the above obscure trap that easily leads to errors. The specific method is as follows:
Class B: public
{
Public:
Void B _nonvirt ();
Void virt ();
}
B;
B * BP = & B;
BP-> nonvirt (); file: // calla: nonvirt
BP-> B _nonvirt (); file: // callb: B _nonvirt
 
Now, B's designers can directly manipulate B's interface. The program requires that the client of B (that is, the code that calls B) be able to call a: nonvirt and B: nonvirt at the same time, which we have done. For object-oriented design (OOD), this is a good practice because it provides a robust interface definition (stronugly defined interface: that is, the interface that does not cause call ambiguity ]. C ++ allows the client programmers to show their skills at the class interface to expand the class. In the above example, the programmer designing B cannot prevent other programmers from calling a: nonvirt. Objects of Class B have their own nonvirt, but even so, designers of Class B cannot ensure that the correct version of nonvirt can be called through the B interface.
 
C ++ also cannot prevent changes made to other parts of the system from affecting B. Suppose we need to write a class C. In C, we require nonvirt to be a virtual function. Therefore, we must change nonvirt to virtual in. However, this will make us useless for B: nonvirt's skills (think about why: d ). For C, the need for a virtual (changing existing nonvirtual to virtual) changes the parent class, which also changes all subclasses inherited from the parent class. This is against the reason why OOP has a low coupling class. New requirements should only have local impact, rather than changing the rest of the system, this potentially damages the existing part of the system.
 
Another problem is that the same statement must maintain the same semantics. For example, for the interpretation of polymorphism statements such as a-> F (), the system calls F (), regardless of whether the object type is A or a subclass. However, for C ++ programmers, they must clearly understand the true meaning of A-> F () When F () is defined as virtual or non-virtual. Therefore, the-> F () statement cannot be independent of its implementation, and the hidden implementation principle is not static. A change to the declaration of F () will change the semantics of calling it accordingly. Independence from implementation means that changes in implementation do not change the semantics of the statement or the semantics of execution.
 
If changes in the Declaration result in changes in the corresponding semantics, the compiler should be able to detect errors. Programmers should keep the semantics unchanged when the announcement is changed. This reflects the dynamic features of software development, in which you will be able to find permanent changes to the program text.
 
Another example that corresponds to a-> F (), where the semantics cannot be kept unchanged is Constructor (See C ++ arm, section 10.9c, P 232 ). However, Eiffel and Java do not have such problems. The mechanism they adopt is simple and clear, and will not lead to the surprising phenomena produced in C ++. In Java, all functions are virtual. To make a method [Translator's note: functions corresponding to C ++] cannot be overwritten, we can use the final modifier to modify this method.
 
Eiffel allows programmers to specify a function as frozen. In this case, this function cannot be overwritten in the subclass.
 
Option 2,
Whether to use an existing function or rewrite one, which should be determined by the programmer who writes the subclass. In C ++, to possess this capability, you must specify virtual as the parent class. For Ood, what you do not want to do is as important as what you want to do. The sooner you decide, the better. This policy prevents errors from being included in the system at the early stage. The sooner you make a decision, the more likely you will be surrounded by assumptions that prove to be wrong in the future; or the assumptions you make are true in one case, however, in another situation, errors may occur, making the software you write more vulnerable and not reusable: software reusability is an important feature for software. For details, refer to object-oriented
Description of the external features of the software in software construct, P7, reusability, Charpter 1.2 A review of external factors ].
 
C ++ requires that we specify possible polymorphism in the parent class (this can be specified through virtual). Of course, we can also import the class in the middle of the inheritance chain to the virtual mechanism, to determine whether a function can be redefined in the subclass in advance. This will lead to problems: for example, functions that are not truly polymorphism (not actually polymorphic) must also be called through table technology with lower efficiency, not as efficient as calling the function directly. [Note: in the context of the article, there is no exact definition of the not actually polymorphic feature. According to my understanding, it should be declared as a polymorphic, the actual action does not reflect a feature such as polymorphic ]. Although doing so won't cause a lot of overhead, we know that in OO programs, there are often a large number of short functions with a single and clear goal, if you accumulate all of these results in a considerable cost. The policy in C ++ is as follows: the function to be redefined must be declared as virtual. Worse, C ++ also said that non-virtual functions cannot be redefined, which makes it impossible for programmers who design and use child classes
Some functions have their own control. [Translator's note: This sentence in the original text is to be elaborated. The original text is written as follows: it says that non-virtual routines cannot be redefined. I guess what the author wants to say is: if you have defined a non-virtual routine in base, then it cannot be virtual in the base whether you redefined it as virtual in descendant.]
 
Rumbaugh and others criticize the virtual Mechanism in C ++ as follows: C ++ has the features of simple inheritance and dynamic method calling, however, a C ++ data structure cannot automatically become object-oriented. Method resolution and the premise of rewriting a function operation in the subclass must be that the function/method has been declared as virtual in the parent class. That is to say, we must predict in the initial class whether a function needs to be rewritten. Unfortunately, the class writer may not expect a special subclass to be defined or will not know which operations will be overwritten in the subclass. This means that when a subclass is defined, we often need to go back and modify our parent class, and make it extremely strict to create a subclass to reuse existing libraries, especially when the source code of this library cannot be obtained. (Of course, you can also define all operations as virtual, and you are willing to pay some small memory costs for function calls.) [rbpel91]
 
However, it is an error mechanism for programmers to handle virtual objects. The compiler should be able to detect polymorphism and generate necessary and potential virtual code for this purpose. It is a burden for programmers to decide whether to be virtual or not. This is why C ++ can only be regarded as a weak object-oriented language (weak object-oriented language): Because programmers must always pay attention to some underlying details (low level details ), these can be automatically processed by the compiler.
 
Another problem in C ++ is the incorrect rewriting (mistaken overriding). Functions in the parent class can be overwritten without knowing it. The compiler should report an error for redefinition in the same namespace unless the programmer who writes the subclass points out that he intended to do so (that is, rewrite the virtual function ). We can use the same name, but the programmer must know what he is doing and explicitly declare it, this is especially true when you assemble your programs and existing program components into a new system. Unless the programmer explicitly overrides an existing virtual function, the compiler must report to us the error of multiple declarations (duplicate Declaration. However, C ++ adopted the original practice of Simula, which has been improved now. Some other programming languages use better and more explicit methods to avoid the emergence of incorrect redefinition.
 
The solution is that virtual should not be specified in the parent class. When we need dynamic binding during runtime, we specify a function to be rewritten in the subclass. The benefit of doing so is that for functions with polymorphism, the compiler can check the function signature consistency. For overloaded functions, the function signature is different in some aspects. The second benefit is that, in the maintenance phase of the program, the initial intention of the program can be clearly expressed. In fact, later programmers often have to guess whether the previous programmer has made any mistake. They chose the same name or they wanted to reload the function.
 
In Java, without the keyword "virtual", all methods are polymorphism at the underlying layer. When methods are defined as static, private, or final, Java calls them directly instead of using Dynamic lookup. This means that they are non-polymorphism functions when they need to be dynamically called. This dynamic feature of Java makes it difficult for the compiler to further optimize.
 
Eifel and Object Pascal cater to this option. In them, programmers who write child classes must specify the redefinition actions they want to perform. We can get a huge benefit from this practice: For people who will read these programs and future maintainers of the program, it is easy to find the function to be rewritten. Therefore, option 2 is best implemented in the subclass.
 
Both Eifel and Object Pascal have optimized the function calling method: Because they only need to generate entry items for calling the allocation table of truly multi-state functions. We will discuss how to do this in the Global Analysis Section.
 
Option 3,
Such a pure virtual function caters to making a function Abstract, so that the subclass must provide a condition for its implementation during instantiation. Any subclass that does not rewrite these functions is also an abstract class. This concept is correct, but take a look at the section pure virtual functions, where we will criticize and discuss this term and syntax.
 
Java also has pure virtual methods (also available for Eiffel). The implementation method is to add deffered annotation to the method.
 
Conclusion:
The main problem with virtual is that it forces the programmer who writes the parent class to guess whether the function has polymorphism in the subclass. If this requirement is not foreseen, or is not included in order to optimize or avoid dynamic calls, the possibility is that it is more closed than open. In the implementation of C ++, virtual improves the coupling of rewriting, leading to a Union that is prone to errors.
Virtual is a difficult syntax to grasp. The related concepts such as polymorphism, dynamic binding, redefinition, and rewriting are easier to grasp because they are oriented to the problem domain itself. This implementation mechanism of virtual functions requires the compiler to establish a virtual table entry in the class, while global analysis is not completed by the compiler, so all the burdens are placed on the shoulders of programmers. Polymorphism is the purpose, and virtual mechanism is the means. Smalltalk, objective-C, Java, and Eiffel use different methods to implement polymorphism.
 
Virtual is an example of chaos in the concept of OOP in C ++. Programmers must understand some underlying concepts, or even learn more about high-level object-oriented concepts. Virtual leaves optimization to programmers. Other methods are implemented by the compiler to optimize the dynamic calls of functions, in this way, the distribution that does not need to be dynamically called (that is, there is no entry in the dynamic call table) can be eliminated by 100%. The underlying mechanisms should be interested in the theoretical and compiler implementers. general practitioners do not need to understand them, or use them to understand high-level concepts. In practice, having to use them is a tedious task and can easily lead to errors, which prevents the software from better adapting to the underlying technology and operating mechanism (see concurrent programs, this reduces software elasticity and reusability.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.