Effective STL ITEM 43: Priority use STL generic algorithm to replace handwriting cycles

xiaoxiao2021-03-06  39

STL generic algorithm vs. Handwritten loop Scott Meyers Preparation Optimization? Don't be so urgent. Scott is trying to let you believe the library function better than you write. -------------------------------------------------- ----------------------------- [This article is from a book that is about to publish. S. Meyers, Effective Stl: 50 Specific Ways To Improve Your Use of the Standard Template Library, modified from Item 26-28 (WQ note, CUJ above, should be Item 43). ADDISON-WESLEY. Issue: Permission of Pearson Education, Inc] Each generic algorithm accepts at least one pair of selectors to indicate the element interval that will be operated. For example, Min_Element () finds the smallest value in this section, and Accumulate () makes some forms of overall summation operations in the interval, partition () segmentates elements within the interval to satisfy and not satisfy certain Two parts of the decision condition. When the generic algorithm is executed, they must check each element in the interval of it, and is performed in the way you expect: from the starting point of the interval to the end point. There are some generic algorithms, such as Find () and Find_if (), may return before traversal, but even these generic algorithms, there is a loop inside. After all, even find () and find_if () must also determine the elements they find after viewing each element in this interval. Therefore, the extensive algorithm is a loop. In addition, the STL generic algorithm involves a wide range of faces, which means that many tasks you have to use to use loops, and now you can use the generic algorithm to achieve. For example, there is a Widget class that supports RedRaw (). Class widget {public: ... void redraw () const; ...}; and, you want RedRaw all Widget objects in a list, you may use such a loop: list lw; ... for (List :: item i = lw.begin (); i! = lw.end (); i) {i-> redraw ();} But you can also use for_each () generic algorithm: For_each (lw.begin (), lw.end (), mem_fun_ref (& widget :: redraw)); for many C programmers, using the cycle is much more natural than calling the generic algorithm, and reading the circulation Understand the address of MEM_FUN_REF and Widget :: RedRaw. However, this article will explain that the generic algorithm is more preferable. In fact, this article will prove that call generic algorithm is usually more superior to handwritten cycles. why? There are three reasons: l efficiency: generic algorithm is usually efficient than cycles. l Propagation: It is more likely to generate an error while writing a generic algorithm. l Maintainability: The generic algorithm usually makes the code cleaner and intuitive compared to the corresponding explicit cycle. The following sections will be exemplified. From the viewpoint of efficiency, the generic algorithm defeated explicit cycles, two main factors, and a secondary factor in three aspects. The secondary factor is to eliminate excess calculations.

Looking back, let's write the loop you just wrote: for (list :: itrator i = lw.begin (); i! = Lw.end (); i) {i-> redraw ();} I already Highlights the loop termination test statement to emphasize each cycle, I must check with lw.end (). That is to say, each cycle must call the function List :: end (). But we don't need to call end () more than one, because we are not preparing to modify this List, is enough to call the end (). And let's turn to see the generic algorithm, you can see that only the correct evaluation of the End () function: // this call evataS LW.END () exactly // ONCEFOR_EACH (LW.BEGIN (), LW .end (), MEM_FUN_REF (& Widget :: redraw)); Reert, STL's implementation knows begin () and end () (and similar functions, such as size ()) is very frequent, so as far as possible It is most efficient to achieve. Almost certainly inlines them, and encoding the most compilers can avoid repetition calculations (by setting the calculation result (this optimization)). However, experience shows that this is not always successful, and when it is not successful, the avoidance of repetition calculations is sufficient to make generic algorithms have performance advantages than handwritten cycles. But this is just a secondary factor affecting performance. The first primary influencing factor is that the implementation of the library can use them to know the specific implementation of the container, and the user cannot optimize the code. For example, the elements in Deque are typically stored on an array of one or more fixed sizes (internal). The pointer-based traversal is faster than the selector, but only the implementation of the library can use the pointer-based traversal because only they know the size of the internal array and how to move the next one from a number of components. There are some implementation versions of STL containers and generic algorithms that are specifically considered their demineral data structure, and it is known that such implementation is 20% higher than "usual". The second main factor is that in addition to the most negligible algorithm, all the mathematical algorithms used by the STL generic algorithm are more complicated than the algorithms that the general C programmers can get, and sometimes much more complicated. It is impossible to go beyond Sort () and its universal algorithm (for example, stable_sort (), nth_element (), etc.); Search algorithms for sequential intervals (for example, binary_search (), loc_bound (), etc.) quite perfect; even It is very ordinary task, such as destroying elements from a vector, deque or array, using the Erase-Remove usual method more efficient than the loop written by most programmers. If the efficiency is not convinced, maybe you are more willing to accept the correctness based on the correctness. When writing a cycle, a more trouble is to ensure that the selected subsection (a) used is valid, and (b) pointing to where you expect. For example, it is assumed to have an array, you want to get every element of it, add 41 above, then insert the result from the front end into a Deque.

With cycling, you may write: // c api: this function takes a pointer // tray of at MOSTRAYSIZE // Doubles and WRITES DATA to It. It // Returns the Number of Doubles Written.Size_t Fillarray (Double " * pArray, size_t arraySize); // create local array of max possible sizedouble data [maxNumDoubles]; // create deque, put data into itdeque d; ... // get array data from APIsize_t numDoubles = fillArray (data , MaxNumdouBles); // for Each I in Data, INSERT DATA [I] 41 // at The Front of D; This Code Has A Bug! for (SIZE_T I = 0; I :: item. (// Insert) Data [I] 41 AT INSERTLOCATION, THEN / INCREMENT INSERTLOCATION; this code is also buggy! for (size_t i = 0; i

After that, you may do this: Deque :: item insertlocation = d.begin (); // update insertation each time // IT IS Called to Keep The iterator valid, // THEN Increment ITFOR SIZE_T I = 0; I (), 41); this "Bind2nd (Plus (), 41)" may spend some time to understand (especially if you do not usually use STL's Bind ", but Sub-related only harassment refers to the starting point and end points of the source interval (and this will not be a problem) and ensure that INSERTER is used on the starting point of the destination interval. Actual experience shows that the correct initial selection is often easier for the source interval and destination intervals, at least more easily than to ensure that the cyclic body is not intended to be inadvertently, it is much more easily. Because you must pay attention to whether you are not properly manipulated or invalid before using the selected child, it is much more representative. Assuming that the use invalid selection will cause the "undefined" behavior, it is assumed that "undefined" behavior during development and testing HAS a Nasty Habit of Failing to show itself, why do you unnecessary danger? Throw the selection to the generic algorithm, let them consider the various strange behaviors when manipulating the selection. I have explained why generic algorithms can be more efficient than handwritten cycles, and also describe why cycles will be difficult to walk in a thorns associated with chosen, while generic algorithms are avoiding this. If you are lucky, you are now a generals of generic algorithms. However, luck is not enough, before I rest, I want to make more sure. Therefore, let us continue to travel to the code clarity. Finally, it is best for software to be the clearest software, the best software, can be most fun to enhance, maintain and apply software for new environments. Although it is accustomed to cycling, generic algorithms have advantages in this long-term competition. The key is the power of the name of the name. There are about 70 extensive algorithms in STL, with a total of 100 different function templates (each overload is calculated). Each generic algorithm completes some carefully defined tasks, and there is reason to think that professional C programmers know (or should go see what each generic algorithm has completed. Therefore, when the programmer calls Transform (), they believe that each element in the interval has a function, and the result will be written to another place. When the programmer calls Replace_IF (), he (she) knows that the object that meets the rules in the interval will be modified. When calling partition (), he (she) understands all objects that satisfy the judgment conditions will be gathered together. The name of the STL generic algorithm conveys a lot of semantic information, which makes them more than the casual loop. The name of the generic algorithm suggests its function.

"For", "while" and "do" can't do this. In fact, this is true for all components of the standard C language or C language runtime. There is no doubt that you can implement strlen (), memset () or bsearch (), but you won't do this. Why not? Because (1) already helps you achieve them, there is no need to do itself again; (2) The name is the standard, so everyone knows what they do; and (3) you guess the library The implementator knows some tips you don't know, so you don't want to miss the skilled library implementors to provide optimization. As you don't write your own version of Strlen () and other functions, it also does not truly use loops to implement an equivalent version of the existing STL generic algorithm. I hope that the story is over, because I think this is very persuasive. Oh, this is a tale what refuses to Go Gentle Into That Good Night. The name of the generic algorithm is much more meaningful than the loop cycle. This is the fact, but it is more likely to use the loop to make people understand the operations on the selection. For example, it is assumed that you want to find out the first element of the first bit of X and Y small than Y. This is the implementation of cycles: Vector v; int x, y; ... // ipil an // appropriate value is found or/ v.end () is ReachedVector :: item i = v.begin (); for (; i! = v.end (); i) {if (* i> x && * i x and val (), bind2nd (Greater (), x), bind2nd (less (), y)))); even if there is no non-standard component, many programmers will also say that it is far less clear, I have to agree to this view. .

Find_if () calls can not be as complex, as long as the test logic is encapsulated into a separate Functor (that is, the Operator () member function class): Template Class BetweenValues: Public std :: unary_function < T, Bool> {public: // Have the ctor save the // VALUES To Be Betweenbetweenvalues ​​(Const T & LowValue, Const T & HighValue): LowVal (LowValue) {} // Return WHether Val IS // Between The Saved ValuesBool Operator () (Const T & Val) Const {Return Val> LowVal && Val iTerator i = FIND_IF (v.begin () , v.end (), betWeenvalues ​​ (x, y)); but this method has its own defects. First, create a BetWeenValues ​​template to make more work more than write cyclicers. On the number of lights. Circulation: 1 line; BetWeenValues ​​Template: 24 lines. It is too uncomfortable. Second, find_if () is looking for something detained from the call, you must really understand this call to Find_if (), you must also view the definition of BetWeenValues, but BetWeenValues ​​must be defined in calling find_if () Outside of the function. If you try to make BetWeenValues ​​in this function, like this, like this, // beginning of function {... template Class BetWeenValues: Public std :: unary_function {...}; vector :: Iterator i = Find_if (v.begin (), v.end (), betweenvalues ​​ (x, y)); ...} // end of function You will find that the compilation is not passed, because the template cannot State in the function inside the function. If you try to use the class instead of the template, avoid this problem, // beginning of function {... Class BetWeenValues: Public std :: unary_function {...}; vector iTerator i = find_if (V .begin (), v.end (), betweenvalues ​​(x, y)); ...} // end of function You will find still not luck, because the class defined inside the function is a local class, and local class You cannot bind the Type parameters of the template (such as the FUNCTOR type required by Find_IF ()). Very disappointed, the Functor class and the Functor class cannot be defined inside the function, whether it is more convenient to implement. In the long-term compass of generic functions and handwriting cycles, the bottom line of code clarity is: This is entirely on what you want to do in the loop. If you have to do, generic algorithms have been provided, or very close to it, call generic algorithms clearer. If the things you have to do in the loop are very simple, but when you call generic algorithms, you have to use a Bind family and Adapter or an independent Functor class. You may still write a loop.

转载请注明原文地址:https://www.9cbs.com/read-74594.html

New Post(0)