Thank you old friends Xie Xuan ("Symbian OS Explained" Translator) Selfless provides the original translation, which is the adjusted version. Because it is an original public style, put it on the blog.
Chapter 34 Index Table Difficulty 5
The index table is indeed a useful customary method (IDiom), and is a technology worth learning to learn. But how can we effectively achieve this technology ... Wait, it should not only "effective", "perfect"?
JG problem
1. Who will benefit from the clear and easy-to-understand code?
Guru problem
2. The following code exhibits an interesting and useful usual method for creating an index table in an existing container. For more detailed explanation, please refer to its original text [Hicks00].
Evaluate this code and find out:
a) "Mechanical" error like invalid syntax or non-portable style habits.
b) What improvements can be made in style to improve the clarity, reuse and maintainability of the code.
// sort_idxtbl (...) The role is to arrange an index array
#include
#include
Template
Struct sort_idxtbl_pair
{
Raiter IT;
INT I;
BOOL Operator <(const sort_idxtbl_pair & s)
{RETURN (* IT) <(* (s.it));
Void set (const raiter & _it, int _i) {it = _IT; i = _i;}
Sort_idxtbl_pair () {}
}
Template
Void sort_idxtbl (raiter first, raiter last, int * pIDxtbl)
{
INT IDST = Last-first;
Typedef st :: vector
V V (IDST);
INT i = 0;
Raiter it = first;
V :: itrator vit = v.begin ();
For (i = 0; IT (* VIT) .SET (IT, I); Std :: sort (v.begin (), v.end ()); INT * PI = PIDXTBL; Vit = v.begin (); For (; vit * Pi = (* VIT) .i; } Main () { INT Ai [10] = {15, 12, 13, 14, 18, 11, 10, 17, 16, 19}; COUT << "##########" << endl; Std :: Vector Int aidxtbl [10]; Sort_idxtbl (vecai.begin (), vecai.end (), AIDXTBL); For (int i = 0; i <10; i ) COUT << "i =" << i << ", AIDXTBL [I] =" << AIDXTBL [i] << ", Ai [AIDXTBL [I]] =" << Ai [AIDXTBL [I]] << endl; COUT << "##########" << endl; } solution Definition 1. Who will benefit from the clear and easy-to-understand code? In short, it is good for everyone. First, the clear code is easier to debug, it is precisely because of the clear, so the code is a lot in the first time, and it is more relaxed to write a clear code, and you can make your life easier. (Related cases, please refer to the discussion of the example 27-3.) In addition, when you reread your code after a month or after a month (if your code is no problem, this is the actual use, this The link is usually inevitable), you will find it easier to "raise" the clear code, what is the code to do. Most of the programmers feel that all the details of the code are remembering and maintaining even a few weeks. Especially after turning to other work, it is more difficult to remember that these are more difficult. After a few months and even years, even if you reread your own code, it is easy to think that it seems to be a stranger wrote (but that "strangers" happens to have the same personal coding style with you). It has been said enough to have a profit. Let's take a look at the aspects of the people: the code maintainer will also benefit from the clarity and readability of the code. After all, you have to maintain the code to "Grok" code. Robert Heinlein, the word "Grok", which means in-depth and complete understanding; here, this word also contains understanding of code itself, and the side effects of code, and other The interaction method of the subsystem. All in all, in the case where there is no fully understood a piece of code, it is too easy to introduce new mistakes. Clear, understandable code is easier to put in it, so the repair of this code is not so fragile, dangerous, and it is not easy to unintentionally introduce side effects that do not want to introduce. However, the most important point is that due to these reasons, your end users will benefit from clear and understandable code: this code is very small from the first error; it is more easily maintained correctly, and in the maintenance process It is not to introduce the same more errors. Guidelines: In general, give priority consider writing clear, correct code. In-depth profiling 2. The following code shows an interesting and useful usual method for creating an index table in an existing container. For more detailed explanation, please refer to its original text [Hicks00]. Evaluate this code and find out: a) "Mechanical" error like invalid syntax or non-portable style habits. b) What improvements can be made in style to improve the clarity, reuse and maintainability of the code. Allow me to repeat: These codes show an interesting and useful customary. I often find that the same container must be accessed in different ways, such as the elements in the same container, followed by different sort criteria. So, this method is indeed useful: Save actual data in a main container (such as Vector After all of which have finished these, let's take a look at what improvements to the code case given. Correct "Mechanical" error a) "Mechanical" error like invalid syntax or non-portable style habits. The primary aspect of constructive criticism is the "mechanical" error in the code. These "mechanical" errors listed below are unable to be compiled by most platforms. #include 1. Spell correctly spell the standard library header file name. In this example, the header file Next, consider: Main () 2. Define the main function correctly. This unmovable main function prototype is never standard C [C 98] style, although it can also be used as a legal compiler extension characteristic (provided the compiler to give a warning). This main function origin is valid before C99, because the C99 (C99) is allowed in C (C ) and C99 [C99] (C99) in C (C ) Decisively, this feature is completely removed from the standard. For C standards, please see: §3.6.1 / 2: The portable code must define the main as an int main () or int main (int, char * []). §7 / 7 Footnotes 78, and 7.1.5 / 2 footnotes 80: Implicit INT is disabled. Appendix C (Compatibility), Note on 7.1.5 / 4: It is clear that main () is invalid in the form of C , and Int main () must be written. Guidelines: Don't rely on implicit int; this is not a standard-portable C . In particular, "void main ()" or "main ()" never is a standard C Writing (although there are still many compilers support them as extensions). COUT << "##########" << endl; 3. Always remember #include you need the type definition header file. This program uses COUT and ENDL but there is no #include 4. Follow the principles of "More Exceptional C " [SUTTER02] on the principle of using the name space (Namespace). For COUT and ENDL, the program must define them in std ::, or write: use std :: cout; using std :: endl ;. Unfortunately, forget the situation of the namespace domain qualifier is still very common. I have to figure out that the original author of this code is limited to Vector and Sort, which is very good. Improving style b) What improvements can be made in style to improve the clarity, reuse and maintainability of the code. In addition to "Mechanical" errors, there are still some places in the code case. If you want me to implement it, I will do it in different ways. First, you have to make two points to the auxiliary structure sort_idxtbl_pair: Template Struct sort_idxtbl_pair { Raiter IT; INT I; BOOL Operator <(const sort_idxtbl_pair & s) {RETURN (* IT) <(* (s.it)); Void set (const raiter & _it, int _i) {it = _IT; i = _i;} Sort_idxtbl_pair () {} } 1. Be sure to guarantee const-correctness. In this example, sort_idxtbl_pair :: Operator Guidelines: Implement the correctness of Const. 2. Eliminate redundant code. The program explicitly writes the default constructor of class sort_idxtbl_pair, but it doesn't differ from the implicitly generated version. So you can simply save. In addition, two data members of sort_idxtbl_pair are public. Although the SET () member function can make the syntax when setting these two members a little better, but because it is called only in one place, it is this Point benefits are not worth introducing this extra complexity. Guidelines: Avoid code repeat and redundancy. Let's go to the core function sort_idxtbl (): Template Void sort_idxtbl (raiter first, raiter last, int * pIDxtbl) { INT IDST = Last-first; Typedef st :: vector V V (IDST); INT i = 0; Raiter it = first; V :: itrator vit = v.begin (); For (i = 0; IT (* VIT) .SET (IT, I); Std :: sort (v.begin (), v.end ()); INT * PI = PIDXTBL; Vit = v.begin (); For (; vit * Pi = (* VIT) .i; } 3. Select meaningful and appropriate names. In this case, sort_idxtbl is a misleading name because this function is not sorting an index table ... but creates an index table! On the other hand, the code uses template parameter name raiter to point out this random access (Random-Access) iterator, which can be a good score; because this random access feature is exactly necessary for this version of the code. So name the template parameter to raiter as a good tip. Guidelines: Select a clear and meaningful name. 4. Ensure consistency. In sort_idxtbl (), sometimes the variable is set at the initial value in the FOR cycle initialization statement, and sometimes it is not the case. This makes the code more difficult to read, at least for me. The benefits brought about this vary from person to person. 5. Eliminate unnecessary complexity. There are several unnecessary local variables in this function! First, the variable IDST is initialized to last-first, but it is only used once; why not write Last-first-first-first to get rid of this confusion? Second, the server's iterator VIT is created. In fact, it can be used to use the next cable to replace, the effect is the same, and the code will be clearer. Third, the local variable IT is initialized to the value of the function parameter first, and after this, the first itself has not been used; I personally prefer to use the function parameters directly (even if you change the value of the parameters, because it is Press value to pass the other name. 6. Reuse (first part): More use of standard libraries. Now, the original program has taken a high score because of the multiplex st: sort, this is quite good. But why don't you use Std :: Copy to manually implement the last loop to complete the copy work? Why do you want to re-use a dedicated sort_idxtbl_pair than std :: pair more comparison functions? In addition to writing simpler, multiplexing also makes your code more readable. Modesty, multiplexed existing code! Guidelines: Understand and use (multiplexed) standard library facilities in any suitable place, rather than yourself. 7. Rear (second part): Let the implementation itself is more readily reused (a stone two bird). In addition to the function itself in addition to the function itself, there is no need to be reused directly. Outer Claus Sort_IDXTBL_PAIR is tightly bundled with its use, it is not independently available. 8. Reuse (Part III): Improve the prototype of the function. The original function prototype is as follows: Template Void sort_idxtbl (raiter first, raiter last, int * pIDxtbl) It points to the output area with a bare int * pointer, and I usually avoid this, I prefer to use the managed storage space (such as vector). However, it is clear: End users must be able to call sort_idxtbl and put the output to a normal array or a vector or something. Obviously, "Output to any container" is the use of iterators, isn't it? (See Terms 5 and Terms 6.) Template Void Sort_idxtbl (Rain First, Rain Last, Out Result) Guidelines: Avoid unnecessary type hardcodes, thereby expanding the reuse of generic components. 9. Reuse (fourth), or called "Try to use! = To compare iterators": When comparing iterators, be sure to use! = ("! =" For various types of iterators) instead of < ("<" Is only valid for random access iterators), of course, unless you really have to use it, you're deliberately only support random access iterators. The original procedure is used to compare iterators, which is no problem with random access iterators, and the original meaning of the program is to create an index meter into the vector and array, and both support random access. However, we have no reason to don't want these features to act on containers that do not support random access, such as List and SET! The reason why the original code cannot be applied to these containers is because it is used As Scott Meyers is described in [Meyers96] Terms 32, "" is programmed under 'future time'. "He discussed: Good software can adapt to changes. It can accommodate new features, transplanted to new platforms, meet new needs, and handle new inputs. Software with such flexibility, robust, and reliability is not a tart. It is designed and implemented after the programmer meets the current needs and pays attention to future possibilities. Such software (can be gently adapted) is those written by people developing in the future. Guidelines: Try to use! = Rather than 10. Unless you really need the old value, you will use the pre-incremental increment. Here, for the iterator, it should be habitually incremented ( i) to avoid rear increment (i ); see [SUTTER 00] Terms 6. It is true that these two practices may not bring essential differences for the original code, because Vector Guidelines: Try to choose the pre-incrementation rather than the rear increment, unless you really need to use the old value. The above have covered most important issues. However, there are some things that can be commented, but I don't think it is necessary to put the attention on these is not very important; for example: product code should have the use and semantic annotations of the documentation and functions, but this is not Suitable for code attached to the magazine, because there will be a better language in the article, more detailed explanation. I deliberately didn't evaluate the style of the main function (this is different from the "Machine" error mentioned above, and if the latter will not correct, the code will not be compiled by compilation), because after all, this main function is just a demo. Tools, it helps readers understand how the cable is used, and the index table itself is the focus. summary Let us change a design on the basis of maintaining the original code. 40 Limit our changes within the "Mechanical" error and basic style of correcting code, then consider the following three improvements. Each of them has its own advantages, disadvantages, and style preferences, there is a corresponding explanation in the code annotation. The three versions of the common point are more clear, more acceptable, more suitable for transplantation: These three points should be very valuable to your company. A improved version of the code in // [hicks00] // #include #include #include // Solution 1: Some basic cleanup work, but still retain the protopular structure of the original code. // The amount of code is reduced to 17 lines (even if you put the public: and private:), the original code is 23 lines. // Namespace solution1 { Template Class sort_idxtbl_pair { PUBLIC: Void Set (const iter & it, int i) {it_ = it; i_ = i;} Bool Operator <(const sort_idxtbl_pair & other) const {RETURN * IT_ <* Other.it_;} Operator int () const {return i_;} Private: ITer IT_; INT i_; } // Most changes are in this function, you can see that this function is now only 5 lines, and there are 13 lines. // After each line of code, I give the original code for comparison. // Try to write a clear and simple code, do not introduce unnecessary complexity and blurry! // Template Void sort_idxtbl (iterin first, iterin last, oreout out) { Std :: vector // int idst = last-first; // typedef st :: vector // v V (IDST); For (int i = 0; i v [i] .set (first i, i); // int i = 0; // raiter it = first; // v :: itrator Vit = v.begin (); // for (i = 0; IT // (* VIT) .SET (IT, I); Std :: Sort (v.begin (), v.end ()); // std :: sort (v.begin (), v.end ()); Std :: Copy (v.begin (), v.end (), out); // int * pi = pIDXTBL; // vit = v.begin (); // for (; VIT // * pi = (* VIT) .i; } } // Solution 2: Use std :: pair instead of reinvention of a secondary class similar to PAIR. // The current code dropped to 12 lines from the original 23 line. Among these, 8 lines of intent are specific to this issue. // The remaining 4 lines are reusable to other circumstances. // Namespace sol ;ion2 { Template Struct comparepair1stderef { Bool Operator () (Const std :: pair {return * a.first <* b.first;} } Template Void sort_idxtbl (iterin first, iterin last, oreout out) { Std :: Vector For (int i = 0; i s [i] = std :: make_pair (first i, i); Std :: sort (s.begin (), s.end (), comparepair1stderef For (int I = 0; i * OUT = S [i] .second; } } // Solution 3: Just to show some details of some details, a multimap is used in the following code to eliminate // Separate steps (translation: because MAP is ordered) and use std :: transform () instead of handwind cycles. / / Still has 13 lines of code, but more code is available. This program uses more space overhead, and it is likely // Time overhead is also larger, so I tend to use the solution 2. Only the program demonstrates the process of looking for an alternative to a problem. // Namespace solution3 { Template Struct comparedref { BOOL Operator () (Const T & A, Const T & B) Const {RETURN * a <* b;} } Template Struct pair2nd { Const u & operator () (const std :: pair } Template Void Sort_idxtbl (Iterin First, Iterin Last, Iteroutiterout Out) { Std :: MultiMap v.insert (std :: make_pair (first, i)); Std :: Transform (v.begin (), v.end (), out, pair2nd } } // The test case is basically no change, but the output result is imported to the output iterator (original is an int *) // The section indicated (here is a vector), while directly utilizing AI as a container. // #include Int main () { INT Ai [10] = {15, 12, 13, 14, 18, 11, 10, 17, 16, 19}; Std :: cout << "###########" << std :: endl; Std :: Vector // use annother namespace name to test a different solvent solution Solution3 :: Sort_idxtbl (AI, AI 10, AIDXTBL.BEGIN ()); For (int i = 0; i <10; i) Std :: cout << "i =" << i << ", AIDXTBL [I] =" << AIDXTBL [i] << ", AI [AIDXTBL [I]] =" << Ai [AIDXTBL [i]] << std :: endl; Std :: cout << "###########" << std :: endl; } 40 original author ([Hicks00] author) also reported feedback from another reader, he showed another elegant, but completely different way: he created an object of a similar container, which contains the original container And it iterator, and allows for iterative access to different sequences (translation: in fact, the Boost library (www.boost.org) has a very heavyweight implementation called Multi_index_Container.