Object-oriented technology first appeared in the Simula 67 System in the 1960 s, and developed well in the Smalltalk system developed by the Paul Alto laboratory in the 1970 s. However, for most programmers, C ++ is the first available object-oriented programming language. Therefore, many of our object-oriented concepts and ideas come directly from C ++. However,C ++When implementing the key polymorphism in object-oriented, we chose a solution that is completely different from Smalltalk. The result is that although both have achieved similar polymorphism, there is a huge difference in practice. Specifically,C ++ PolymorphismThe implementation is more efficient, but not suitable for all occasions. Many inexperienced C ++ developers do not understand this truth and forcibly use the C ++ polymorphism mechanism in inappropriate situations. They are unable to extricate themselves from the trap of reducing their foot. This article will discuss in detail the limitations of the C ++ polymorphism technology and solutions.
Implementation Technology for two different virtual method calls
C ++ polymorphism is the basis for implementing object-oriented technology in C ++. Specifically, when a pointer to the base class calls a virtual member function, the runtime system can call an appropriate member function implementation based on the actual object pointed to by the pointer. As follows:
- ClassBase {
- Public:
- Virtual VoidVmf (){...}
- };
- ClassDerived:PublicBase {
- Public:
- Virtual VoidVmf (){...}
- };
- Base * p =NewBase ();
- P-> vmf ();// Call Base: vmf
- P =NewDerived ();
- P-> vmf ();// Called here
- // Derived: vmf
- ...
Note that the two lines highlighted in the Code call different function implementations even though their syntax is identical. The so-called "polymorphism" refers to this. This knowledge is well known to every C ++ developer.
Now let's assume that we are the real person of the language. How should we implement this polymorphism? It is not difficult for us to get a basic idea. The implementation of polymorphism requires that we add an indirect layer to intercept the call to the method in this indirect layer, and then call the corresponding method implementation based on the actual object pointed to by the pointer. In this process, we manually
The added indirect layer is very important. It needs to complete the following tasks:
1. obtain all information about a method call, including the method to be called and the actual parameters to be passed in.
2. Obtain the actual object to which the pointer references when a call occurs.
3. Find the appropriate method implementation code and execute the call according to the information obtained in steps 1st and 2.
The key here is how to find the appropriate method to implement the Code in step 1. Because polymorphism is about objects, we need to bind the appropriate method implementation code with objects during design. That is to say, a query table structure must be implemented at the object level. Based on the object and method information obtained in steps 1 and 2, find the actual Method Code address in the search table and call it. Now the problem has changed. We should find the problem based on the information. There are two different solutions for this problem: one is to search by name, and the other is to search by location. It seems that there is no big difference between the two ideas, but in practice, these two different implementation ideas lead to a huge difference. Next we will examine it in detail.
In dynamic object-oriented languages such as Smalltalk, Python, and Ruby, the actual method search is based on the method name. The table structure is as follows:
Because such a query table performs a method query based on the method name, the query process involves a string comparison, which is less efficient. However, this kind of table searching has a prominent advantage, that is, high utilization of effective space. To illustrate this, we assume that there are 100 methods in a Base class Base that can be rewritten by the derived class. Therefore, there are 100 Methods shared by all Base objects ), one of its Derived classes, Derived, only intends to rewrite five of the five methods. Therefore, the method lookup table of the Derived class object only requires five items. When a method is called, runtime searches for strings in the five-way method lookup table based on the called method name. If the method is found in the lookup table, run the call, otherwise, forward the call to forward) to the Base class for execution. This is the standard action for calling virtual methods. When the number of methods actually rewritten by the derived class is small, you can arrange the query table as a linear table and compare the order of the query. In this case, the effective space usage reaches 100%. If the number of methods actually rewritten by the derived class is large, you can use the hash function. If you use a reasonable hash function, the space utilization is also very high, and generally it can be close to 75% ).. to quickly find the method. It should be noted that the compiler can easily obtain the names of all the methods to be rewritten, so it can execute the standard gperf algorithm to obtain the optimal hash function.