Misunderstood C ++ -- hanniba
By Mo Huafeng
In August 2, 216 BC, in the eastern plains of Italy, a region called kanni, the two armies opened their battles and were ready to fight. One Party is the Romans led by the governors of Boros and Varo, and the other is the Taiji army and its allies led by hanniba Baka, a great military genius. More than 80 thousand of the Romans, and more than 40 thousand people in cartski. In the evening, however, when the Romans were thoroughly defeated and 70 thousand were killed, only a few were able to escape. This is the famous camini battle. After this battle (plus the previous battle of Lake terbia and trasimeno), the loss of adult citizens amounted to 1/5, a great injury to the Romans. Some of the city-states betrayed Rome, and sisilisi also had an uprising. Rome has reached the point where it is at a leeching level.
Those victories of hanniba benefited from a previous extraordinary expedition. On October 16, 218 BC, hanniba led the army to depart from the New Town of chitaki (Spain), turning over the pyrenniu and entering the South gaolu region. There are two roads in front of him to move over the Armanis mountains or enter Italy along the coast. However, at that time, the Romans had deployed two troops in coastal areas to intercept hanniba. What's more, the Roman naval superiority allows them to land an army behind him at any time. Turning over the Armanis is a difficult road, not to mention in winter.
Hanniba chose the Alves mountains. He opened the Romans and crossed the Armanis mountains between San Benard and Geneva to enter Italy. At this time, the Romans lost their strategic depth, and a sharp knife was deeply inserted into their abdomen...
In the development history of C ++, there is also an expedition like hanniba turning over the armanse mountains. Everything has to begin with the C with class era.
Bjarne has repeatedly stressed that he created C ++ to combine the abstract capabilities of simula with the performance of C. Therefore, on the basis of the C language, a language with object-oriented mechanisms, such as class ownership, inheritance, and overloading, was born. At this stage, C ++ provides two abstract capabilities. One is data abstraction, that is, the meaning of the data to be expressed through the type and the member expressions attached to the type. The other is polymorphism, the most primitive polymorphism (heavy load ).
Data abstraction is achieved through the technology called "abstract data type (ADT. An ADT solution is class. Class provides encapsulation that separates the external features or semantic expressions of a data entity from specific implementations, such as data storage. In this way, the added middle layer isolates the users of the data from the real-time users of the data, so that they can use the common agreed semantic work and no longer understand each other's details, so that the two can be decoupled.
Polymorphism is a more basic and important feature. Polymorphism allows us to use the same symbol to implement some definite semantics. The essence of polymorphism is to express a semantics in one form. Before that, we were forced to use different symbols to represent the same abstract semantics to adapt to the constraints imposed by a strong type system. For example:
// Code #1
Int add_int (int lhs, int RHs );
Float add_float (float LHS, float RHs );
Obviously, the meanings of these two functions are "add two int type values together" and "add two float type values together ". Both semantics are abstracted to express a meaning: plus.
We do not care whether the calculated number is an integer or a real number. Similarly, if you do not consider the type of arithmetic operation objects during programming, it is much easier to care who and who perform what operations. After C ++ introduces overload, this kind of desire can be realized:
// Code #2
Int add (int lhs, int RHs );
Float add (float LHS, float RHs );
Overload means that we only need to care about the semantics of "add". As for what type and what type are added, the compiler automatically parses the operators based on their types.
In a sense, heavy load is a language feature that has been ignored for a long time but is extremely important. In most books about Oop, heavy loads are often used as attachments to OOP and placed in inconspicuous places. Its polymorphism nature is also passive. However, the important role of heavy load is embodied in the practice. Heavy load can be seen as the first step for a language to enter a modern abstract system. Its practical utility is even more than the commonly-used Oop, without having to abstract it like Oop, but with no small side effects.
With the introduction of virtual functions, C ++ began to have a controversial dynamic polymorphism technology. Virtual functions are a type of polymorphism that is attached to classes (the foundation of OOP types. The technical basis is late-binding ). When Class D inherits from Class B, it has two methods to overwrite a function on Class B:
// Code #3
Class B
{
Public:
Void fun1 ();
Virtual void fun2 ();
};
Class D: Public B
{
Public:
Void fun1 ();
Void fun2 ();
};
When the inherited Class D declares the member functions with the same name and signature as the member functions of the base class B, the member functions of the base class will be overwritten. For non-virtual member functions of the base class, the inheritance class directly masks them. For users of Type D, fun1 represents the semantics given in D. The instance of Type D can be implicitly converted to reference B of type B. In this case, the fun1 definition of Class B is called, rather than the fun1 of class D, although B actually points to a D instance at this time.
However, if the inherited class overwrites the virtual function of the base class, the opposite result will be obtained: When you call fun2 that references B, it actually calls the fun2 definition of D. This indicates that overwriting a virtual function will be performed at all layers between the inheritance class and the base class. This thorough and comprehensive coverage behavior allows us to modify or extend the functions or actions of the base class on the inheritance class. This is the basis of the OOP extension mechanism. This technology is called dynamic polymorphism, which means that the semantics expressed by base class references does not depend on the base class itself, but comes from the actual object to which it points, so it is "polymorphism. Because an object pointed to by a reference can be changed at runtime, it is "dynamic.
The "by-product" that comes with the dynamic polymorphism has actually become the core and pillar of OOP. The "dynamic polymorphism" feature of virtual functions leads us to an extreme situation: one is a class of virtual functions. More importantly, virtual functions in this class are not implemented, and each virtual function does not point to a real function body. Of course, such classes cannot be directly used. Interestingly, this class is called an abstract base class,ForceWe inherit from it and "replace it" to implement virtual functions that are not implemented. In this way, the reference of an abstract base class has the behavior of inheriting classes in a multi-state. In turn, abstract base classes actually startForceThe role of the inheritance class to implement certain functions. Therefore, the abstract base class plays a roleInterface. The interface has two roles: 1. the constraint inheritance class (the implementer) forces it to implement the predefined member functions (functions and actions ); 2. describes the member functions (functions and actions) that the inherited class must possess ). These two effects make interfaces a pillar of the OOP design system.
C ++'s progress in this area makes it a real language capable of modern abstraction. However, this kind of progress is not "turning over the almanas mountains ". At best, it can only be counted as "turning over the pyrenniu ". For C ++, the really tough expedition is just getting started, and the daunting "Armanis mountain" is still far ahead.
Like hanniba, when C ++ entered the "Modern abstract language club", there were two choices. You can also build and supplement your infrastructure to become an OOP language, or move on to the steep mountain. C ++'s hanniba -- Bjarne stroustrup -- selects the latter.
From the description of D & E, we can see that "type parameters" have been taken into account in the original design of C ++. However, it was not until the beginning of 1990s that the template was actually implemented. However, the template is only the first step. Similar mechanisms (generic and generic) are available in languages such as Ada, but they did not have a fundamental impact on the current programming technology.
Key results come from Alex Stepanov's contribution. Stepanov's efforts on the algorithm-container library, later known as STL, brought a new programming technology-generic programming (GP)-into people's horizons. The emergence of STL has had an extremely important impact on the template mechanism of c ++, promoting the birth of template specialization. On the surface, the template is a helper feature, but in fact it is more essential than the "type parameter" function.
Suppose we have a group of functions to compare the sizes of two objects:
// Code #4
Int compare (int lhs, int RHs );
Int compare (float LHS, float RHs );
Int compare (string LHS, string RHs );
Overload allows us to use only one compare function name to perform different types of comparison operations. However, these functions have the same implementation code. The introduction of the template allows us to eliminate this repeated code:
// Code #5
Template Int compare (t lhs, t RHs ){
If (LHS = RHs)
Return 0;
If (LHS> RHs)
Return 1;
If (LHS
Such a template can be applied to any type. It not only expresses a semantic with a symbol, but also replaces many repeated codes with an implementation. This is the basic role of GP.
The next change can be regarded as a true "Mountain.
If two pointers exist, they point to two objects of the same type. If we use the above Compare function template, we will not be able to get the required results. This is because we compare the values of the two pointers instead of the object itself. To cope with this special situation, we need to do "special processing" for compare ":
// Code #6
Template Int compare (T * LHS, T * RHs ){
If (* LHS = * RHs)
Return 0;
If (* LHS> * RHs)
Return 1;
If (* LHS <* RHs)
Return-1;
}
This "special version" Compare responds to any type of pointer. If the real parameter is a pointer during the call, the compare of this "pointer version" will get a priority match. If we change compare to the following implementation, it will be very interesting:
// Code #7
Template
Struct comp_impl
{
Int operator () (t lhs, t RHs ){
If (LHS = RHs)
Return 0;
If (LHS> RHs)
Return 1;
If (LHS
When we call compare using the pointer as a real parameter, the magic thing happens:
// Code #8
Double ** X, ** y;
Compare (x, y );
Compare successfully stripped two pointers and correctly compared the values of the two objects. This trick makes full use of the local special and special parsing rules of the class template. According to the rules, the more special the template is, the more priority it is to match. T *'s comp_impl is more "special" than T's, and will get a priority match. When comp_impl is instantiated for a pointer, it will match comp_impl of T *, because the pointer is also a pointer. T * uses the local special mechanism to strip the first-level pointer and instantiate comp_impl with the obtained type. If the pointer of a pointer is stripped away from the first-level pointer, it will match the T * version. The T * version will remove the first-level pointer, and the rest is the actually comparable type-double. In this case, the double cannot match the T * version and can only match the basic template to perform a real comparison operation.
This wonderful technique is the result of some more essential mechanisms in template specialization. This unexpected "template derivative product" can be regarded as a computing capability during compilation. Later, some "good examples" were developed into an independent "template meta programming, TMP ).
Although TMP is novel and mysterious, it is only an auxiliary technology to make up for some defects of C ++ and make some extensions. However, it brings us two important inspirations: 1. We may use some mechanisms of the language itself to perform metaprogramming; ii. metaprogramming can be used together with common languages to a certain extent. These revelations provide great guidance for the development of programming languages.
Templates and special rules are the core of C ++ GP. These language features do not come out of thin air. In fact, there is a "behind-the-scenes player" operating everything in the dark.
Suppose there is a type system that contains N types: T1,..., TN, then these types constitute a set T = {T1,..., tn }. When we use the overload technology, we actually construct a set of tuple types to map to function implementation:
The Code #6 and the T * template in code #7 actually construct -> FP () ing. Here, TP is a subset of T: TP = {T' | T' = Ti *, Ti }. In other words, specialization refined the generic system. With the special template technology, we have been able (clumsy) to distinguish floating point numbers, integers, built-in types, built-in arrays, classes, enumeration and other types. The ability to divide data types, that is, the ability to construct different types of subsets.
Now, we can construct a "generic system": g = {t} u t tp u ta u Ti u tf u tc .... Among them, TP is all pointer types, TA is an array, Ti is an integer, TF is a floating point, TC is a class, and so on. However, if we arrange the elements in G according to the generalization degree: {T, TP, Ta, Ti,..., T1,..., tn }. We will find some "faults" in the middle ". These faults are located between T and TP, and between Tp and Ti. This indicates that in C ++ 98/03, the abstract system is incomplete and has defects.
So far, C ++ has not really turned over the most dangerous mountain in alicloud. This is exactly what C ++ 0x is trying to do, and the victory is in sight.
In C ++ 0x, the cool introduced the concept support of first-class. Currently, concept does not have a formal legal description (and a reasonable Chinese translation ). In layman's terms, concept describes a type of (Interface) features. Specifically, a concept describes the public member functions required for the type and the free functions (and operators) that must be applied to the type ), and other necessary features (through indirect means ). The following is a typical concept:
Concept has_equal
{
Bool T: equal (T const & V );
};
This concept tells us that the type it describes must have an equal member, with another object of the same type as the parameter. When we apply this concept to a function template and use it as a constraint on the type parameters, it indicates the requirements of this template for the type parameters:
Template Bool is_equal (T & LHS, t const & RHs ){
Return LHS. Equal (RHs );
}
If the real parameter object type does not have an equal member, is_equal will reject Compilation: this is not what I want!
Concept can be combined. The formal term is "refine ". We can further construct a concept with stronger constraints through refine:
Concept my_concept : Has_equal , Defaultconstructable , Swappable {}
The concept obtained by refine will "inherit" all the constraints of the "base concept. As a more detailed combination of methods, concept can also be passed! Concept constraints of the operator "Remove:
Concept my_concept1 : Has_equal ,! Defaultconstructable {}
This concept requires that the type have equal members, but cannot have Default constructors.
Using these methods, concept can be a set of "Infinite subdivision" types. Theoretically, we can create a series of concept with only one difference function or with only one difference parameter.
A concept is actually the constraint that constitutes the division of the type set T: Tx = {Ti | Cx (Ti) = true, Ti }. CX is the constraint constructed by concept. Different concept have different range constraints. In this way, we can use concept to enumerate all subsets of the type set T. These subsets fill the faults in the above G. In other words, concept refined the granularity of the type division, or the granularity of the generic type. This makes the "discrete" generic system "continuous.
When we use concept to constrain the type parameters of a function template, it is equivalent to building a ing using the type subset described by concept:
In the current stage, C ++ has almost reached the peak of "abstract Alves mountains. However, just as hanniba entered Italy, he had to fight against the mighty Republic of Rome. In front of C ++, we need to further turn the advantages into victory. There are still many things to do. The most important thing is to build a runtime GP. Currently, C ++'s GP is the compile-time mechanism. For running tasks, we also need to turn to the dynamic polymorphism of OOP. However, the C ++ field has begun to work on runtime GP and runtime concept. The latest results in this area can be viewed here, here and here. I believe that after several years of efforts, GP will be completely mature and become the mainstream programming technology.
After the canyi battle, hanniba had an absolute advantage. The Romans have been defeated, and there is no strong enemy force between them and the City of Rome. The ROANS have been frightened and have no fighting spirit. However, hanniba has made a mistake that may make his life regret. He spared the City of Rome and turned to attack the city-state and alliance in the south of Rome. He underestimated the will of the Romans and the strength of the Roman league. The Romans soon ended the initial chaos, appointed new governors, and adopted a new strategy of being firm and firm, with flexibility. As time went by, hanniba and his army were forced to fight for survival. Even though the city was forced to land and take over several cities in Rome, it could not be again given a fatal blow to the Romans.
The strategic mistake of hanniba was actually doomed before the departure of the New Town of chitaki. It is because the fundamental purpose of hanniba's expedition to the Romans is not to defeat and occupy Rome, but to weaken their forces and disband their consortium by attacking Rome. To sign a peace agreement. However, this limited strategy leads to his final failure.
Unfortunately, C ++ has more or less the same strategic error as hanniba. The initial objective of C ++ is basically limited to "Better C ". And fully compatible with C. This seemed reasonable at the time, because C could be regarded as the most successful "underlying high-level language" with high performance and flexibility. However, the design of C does not consider the future expansion of a language called "C ++. As a result, many of the advantages of C are the advantages, but they become the burden of C ++. For example, C uses operators to express the syntax structure in a large amount. For C, it is very concise, but for C ++, it is forced to reuse operators on a large scale, this laid the groundwork for many subsequent syntax defects. In this regard, Ada is relatively mature. It inherits the main syntax from Pascal, but does not consider compatibility. This makes Ada more complete and easy to develop. New languages are new languages. Excessive compatibility is not an advantage. In addition, reasonable inheritance of syntax can also attract a large number of developers. From the perspective of experience, programmers have a strong ability to withstand syntactic changes. They are more concerned about the functions and ease of use of the language.
On the other hand, C ++ initially did not set the goal to "Create a language that is highly abstract and ensures performance ". Throughout the development of C ++, various abstract mechanisms are not added to the language under the guidance of a complete plan or road map. All advanced features are added to the language with a "Fuel-filling tactic. To some extent, C ++ is more like an experimental language than an industrial language. The powerful functions and advantages of C ++ are obtained through long-term accumulation, and many of its defects are also the result of long-term feature addition.
Hanniba and C ++ give us a good lesson. For a language that tries to grow healthily after 1 or 20 years, it is necessary to have a clear objective and Technical Development Plan at the very beginning. In the past, the language features should be preferred. And have enough foresight in technology. We know that technology foresight is hard to achieve. After all, technology is growing too fast. If you can't do it, you have to have enough courage to choose from the past. The so-called "small, big, abandon the child to compete first ".
In general, C ++ is successful in the Development of abstract mechanisms. Despite the many technical flaws, the abstract capabilities of C ++ can be distinguished in many languages. In addition, C ++ is still developing. It is not clear what kind of development will take place in the future. However, whether C ++ continues to repair, supplement, or fundamentally change, its abstract capabilities will be retained without compromise and will be continuously improved and enhanced.
After the canI battle, hanniba won several small-scale victories. However, after a long period of operation, we were not supported by cartski. As hanniba became weaker and weaker, we had to survive on the Italian peninsula. Rome quickly recovered its vitality, reformed its military system and combat methods, and regained its strategic initiative. More importantly, Rome also has its own "hanniba"-(conquering Africa) probrius colnelius sipi'a (dasipi'a ). Kota Kinabalu was sent to the North African continent and directly attacked the old nest of the cartji people. Hanniba was recalled, and a decisive battle was held in Zama and sipi'a. In the end, Kota Kinabalu used the tactics learned from hanniba to defeat the gitji and won the second victory in the War of the Communist Party of China.
Later, hanniba went on the coast of the Mediterranean and sought a chance to make a comeback. But in the end, he was forced to complete his work on February 26, 183 BC at the age of 64. Interestingly, his 12-year-old rival, sipi'a, also died in the same year. A great legend ends.