Effective STL Terms 5

zhaozj2021-02-11  191

Terms 5: Try to use interval member functions instead of their single element brothers

fast! Given two VECTOR, V1, and V2, what is the simplest way to make V1 content and the second half of V2? Don't be "more than half of the V2 has an even element", as long as you do some reasonable things.

time up! If your answer is

V1.assign (v2.begin () v2.size () / 2, v2.end ());

Or others are very similar, you will answer, you can get a gold medal. If your answer involves more than one function, but does not use any form of loop, you are close to the correct answer, but there is no gold medal. If your answer involves a loop, you need to spend some time to improve. If your answer involves multiple loops, then we can only say that you really need this book.

By the way, if you answer the answer to this question contains "Well?", Please pay great attention because you will learn something really useful.

This quiz is designed to do two things. First, it provides me a chance to remind you that the Assign member function exists, too many programmers have not noticed this is a very convenient way. It is valid for all standard sequence containers (Vector, String, Deque, and LIST). Whenever you have to fully replace the content of a container, you should think of assignment. If you just copy a container to another with type, Operator = is the selected assignment function, but for the example of the demonstration, when you want to give a fully new data set, Assign can be used, but Operator = Can't do it.

The second reason for this test is to demonstrate why interval member functions take precedence over their single-element replacement. A interval member function is a member function like the STL algorithm, using two iterator parameters to specify a range of elements to perform a certain action. Do not use the interval member function to solve the problem starting with this terms, you must write an explicit loop, possibly like this:

Vector V1, V2; // Suppose V1 and V2 are Widget's Vector

v1.clear ();

For (Vector :: Const_Iterator Ci = V2.BEGIN () V2.Size () / 2;

Ci! = v2.end ();

CI)

v1.push_back (* ci);

Terms 43 Detail verify why you should try to avoid handwriting explicit loops, but you don't have to read that clause to know how much work is written more than Write Assign call. Just like we will see it immediately, the loop will also cause a loss of efficiency, but we will handle it.

A method of avoiding the loop is to use an algorithm in accordance with the recommendations of the Terms 43 instead:

V1.clear ();

Copy (v2.begin () v2.size () / 2, v2.end (), Back_INSERTER (V1));

Write these still more work more than the call to Assign. In addition, although there is no loop in this code, there is a loop in COPY (see Terms 43). As a result, the efficiency loss still exists. I will also discuss below. Here, I want to find almost all target intervals is the use of the COPY specified by inserting iterators (such as through INSERTER, BACK_INSERTER, or Front_Inserter). Should be - replaced by calling interval member functions. For example, this COPY call can be replaced with an intervail version of INSERT:

v1.insert (v1.end (), v2.begin () v2.size () / 2, v2.end (); this input is less than calling COPY, but it happens directly to it : Data insert V1. Calling Copy also expressed the meaning, but not so straightforward. This focuses on the wrong place. For what happened, it should not be an element being copied, but there is a new data to add V1. The INSERT member function makes this clarity. The use of COPY becomes embarrassed. There is no good attention to this fact that there is a copy of this fact, because STL builds will be copied in things. Copy is very basic for STL, it is the subject of this book 3!

Too many STL programmers use COPY, so I repeat my suggestion: Almost all target intervals are inserted into the use of the iterator, can be replaced by the calling interval member function.

Return to our Assign example, we 丫    鼍 × thirsty  涑 涑 焙  嫠 嫠  堑 ピ ピ 匦 匦 艿 艿 艿 艿 碛 碛 碛 ピ 匦 匦 匦 匦 匦 艿 艿

Generally speaking, using the interval member function can enter fewer code. The interval member function will cause the code to be clearer and more direct.

In short, the code generated by the interval member function is easier to write, it is easier to understand. Not like this?

Hey, some people will regard this argument as programming, and developers like the style of argument, almost and like to argue, what is the true editor. (Although there are many questions, it is indeed Emacs.) If there is a determination interval member function takes precedence than their single-element brothers will be very beneficial. For standard sequence containers, we have one: efficiency. When dealing with a standard sequence container, the application single-element member function requires more memory allocation, more frequently copy objects than the interval member function that does the same purpose, and / or / or causing excess operation.

For example, suppose you want to copy an int array to the front end of the Vector. (Data may be stored in one array instead of vector, because the data comes from legacy C API. Discussion on the problem of using STL containers and C APIs, see Terms 16.) Use the Vector Interval INSERT function, it is really insignificant:

INT DATA [Numvalues]; // Assume NumValues

// Other places definition

Vector v;

...

v.insert (v.begin (), data numvalues; // put the int DATA

// Insert V front

Use iteration calls in an explicit loop to insert, which may seem to be less like this:

Vector :: Iterator INSERTLOC (v.begin ());

For (int i = 0; i

INSERTLOC = V.insert (Insertloc, Data [i]);

}

Note We must carefully save the INSERT return value for next loop iteration. If we don't update insertloc after each insert, we will have two questions. First, all the loop iterations after the first time will result in undefined behavior, because INSERT will make Insertloc every time I call INSERT. Second, even if INSERTLOC is maintained, we are always inserted in the front of the Vector (i.e., in v.Begin ()), such a result is an integer to copy to V in a reverse sequence.

If we follow the guidelines of the Terms 43, use the calling Copy instead of the loop, we will get something like this:

Copy (Data, Data Numvalues, Inserter (v, v.begin ()));

This time, this code is based on COPY, which is almost the same as the code using the explicit loop, so we are in the purpose of efficiency analysis, we will pay attention to the display cycle, keep in mind the analysis is also the same as the code of COPY. Focusing on explicit cycling, it can be more easily understood. Yes, that is "Impact (S)" plural, because the code using the INSERT single element version of the code has levied three different performance taxes, and if you use the interval version of INSERT, there is no. The first tax is that there is no necessary function call. Insert NumValues ​​Elements into V, each time one, naturally spend your Numvalues ​​times call INSERT. Using Insert interval, you will save the Numvalues-1 call as much as you spend a call. Of course, possible inner links will save you this tax, but again, it may not. Only one thing is determined, using Insert interval, you know that you don't have to spend this.

Inline also can't save your second tax - non-efficiently moving the existing elements in V to the overhead of their final insertion. Each time you call INSERT to add a new element to V, each element above the insertion point must move upwards to make a space for the new element. Therefore, the elements at position P must move up to position P 1, etc. In our example, we insert Numvalues ​​elements at the front of V. That means moving a total of NumValues ​​positions in each of the elements before V. However, each time INSERT calls can only move up to one position, so each element will be moved NumValues ​​times. If V has n elements in front of the insertion, a total of N * NumVALUES will occur. In this example, V accommodates int, so each move may be attributed to a MEMMOVE call, but if V accommodates the user-defined type, such as a widget, each move will cause the type of assignment operator or copy constructor. (Most of them call assignment operators, but each time the last element is moved, the move is done by calling the element's copy constructor.) In general, insert NumVALUES a new object The front of the VECTOR accommodated N * Numvalues ​​Supreme Call: (N-1) * NumValues ​​Call widget assignment operator and NumValues ​​call widget copy constructor. Even if these calls are inline, you still do the work of the elements in Numvalues.

Conversely, the standard requirement interval INSERT function moves existing elements to the last position, that is, the overhead is moving once. A total overhead is the copy constructor of the object type in the NumValues ​​subtle, and the remaining is the type of assignment operator. The interval INSERT is less moved less than the single element insertion strategy. It takes a minute to think about it. This means that if NumValues ​​is 100, the interval of Insert is less than 99% of the code in the form of single element forms that call INSERT!

I have a small correction before I turn to a single-element member function and the third efficiency overhead of their intervals. I have a truth in the previous paragraphs, and there is no other than the truth, but it is not the truth. When you can determine the distance between the two iterators, you can determine the distance between the two iterators, one interval INSERT function can move an element to its final position in one move. This is almost always possible because all forward iterators provide this feature, and the forward iterator is almost everywhere. All iterators for standard containers provide a function of forward iterators. The iterator of non-standard HASH containers (see Terms 25) is also. The pointer in array is an iterator also provides such a function. In fact, the only standard iterator that does not provide forward iterator capability is an input and output iterator. Therefore, in addition to the iterator in the form of an INSERT interval is an input iterator (such as ISTREAM_ITERATOR - see Terms 6), I have truthful things above. In that unique case, the interval insert must move the elements to their final position each time, and the advantages in the expectation will disappear. (For output iterators, this problem does not happen because the output iterator cannot be used to specify a section for INSERT.) The last performance tax is stupid, repeats the single element inserting instead of one interval, must be processed Memory allocation, although there is also an annoying copy in it. As explained in Terms 14, when you try to put an element into the multi-memory Vector, this vector will allocate new memory with more capacity, copy its elements from the old memory to the new memory, destroy the old memory The elements of the elements are recycled. Then it adds inserted elements. Terms 14 also explain that most of the VECTOR implementations have doubled their capacity when using memory, so inserting NumValues ​​will result in the allocation of up to Log2NumVALUES. Terms 14 also focused on existing implementations of the behavior, so that 1000 elements inserted each time cause 10 new allocations (including their responsible for the elements). Compared with it is (and, it is predictable), and a range insert can calculate how much memory needs to be calculated before starting to insert something (assuming forward iterator), so it doesn't have more than Re-allocate the intrinsic memory of the Vector at once. Just like you can imagine, this savings is quite considerable.

I have just conducted an analysis is used for Vector, but the same reason also acts on String. For Deque, the reason is also very similar, but DEQUE manages the way their memory is different from the vector and String, so the argument of repeated memory allocation cannot be applied. However, the argument about the movement of unnecessary elements often is usually applied by observations for the number of functions (although the details are different).

In the standard sequence container, there is a List, where there is a performance advantage in the form of the INSERT interval. The arguments for repetitive function calls are of course to be valid, but because of the working mode of the linked list, copy and memory allocation issues have not occurred. Instead, there is a new problem here: it is repeatedly assigned a NEXT and PREV pointer to some nodes in the list.

Whenever an element is added to a linked list, the chain table node holding an element must have its next and prev pointer set, and of course the node in front of the new node (we call it B, "Before") must set its NEXT pointer. The node behind the new node (we call it A, "after") must set its prev pointer:

When a series of new nodes are added by calling a single-element INSERT of the List, other new nodes other than the last one will set it twice, the first point to A, the second point to insert it behind it element. Every time I insert a front of A, it sets its prev pointer to a new node. If the NumValues ​​node is inserted into A, the NEXT pointer inserted into the node happens NumVALUES-1 multi-assignment, and A's prev pointer will occur in NumVALUES-1. Total 2 * (NumValues-1) There is no necessary pointer assignment. Of course, the pointer is very lightweight, but if it is not a must, why do you spend them? It is now clear that you can do not have to avoid the key to avoiding the use of List's INSERT intervals. Because that function knows how many nodes will be inserted, it can avoid excess pointer assignments, and you can set the values ​​after each pointer to use only one assignment.

For standard sequence containers, there is a lot of things in addition to the programming style when selecting a single element insertion and interval insertion. For associated containers, efficiency problems have little, but the expenditure of the additional duplicate calls is still existing. In addition, the special type of interval insert can also be optimized in an associated container, but as far as I know, such optimization currently exists in theory. Of course, when you see this, theory may have become practiced, so the interval insertion of the associated container may become more effective than insertion of single elements. There is no doubt that they don't reduce efficiency, so you choose them without any losses.

Even if there is no efficiency, use the interval member function when you write the code, you need fewer inputs. This fact still exists. It is also easier to understand, thereby enhancing your software's long-term maintenance. As long as the two features are enough to allow you to choose the interval member function. The efficiency advantage is really just a dividend.

After experiencing the long story of the miracle of the interval member function, I only need me to summarize you. Know that the member function support interval allows you to find it easier to discover the timing of using them. In the following, parameter type Iterator means the iterator type of the container, that is, Container :: Iterator. On the other hand, the parameter type InputIterator means accepting any input iterators.

Interval constructor. All standard containers provide this form of constructor:

Container :: Container (starting point of InputIterator Begin, //

Inputiterator End); // Terminal

If the iterator transmitted to this constructor is iStream_iterators or iStreamBuf_iterators (see Terms 29), you may encounter the most amazing resolution of C , one is that your compiler may declare this structure as a function declaration. Instead of being defined by a new container object. Terms 6 tells you that you need to know all about parsing, including how to deal with it.

Interval insertion. All standard sequence containers provide this form of INSERT:

Void Container :: INSERT (Iterator Position, // Interval inserted

InputItemrator begin, // Insert the starting point of the interval

Inputiterator End; // Insert the end point of the interval

Associated containers use their comparison functions to determine where the elements are to be placed, so they omit the Position parameters.

Void Container :: Insert (lnputiterator begin, input);

When looking for a method of replacing a single element insertion, don't forget that some single-element variables are camouflage themselves with different function names. For example, Push_Front and Push_Back are inserted into the container, even if they are not called INSERT. If you see a loop call push_front or push_back, or if you see an algorithm - such as the COPY - the parameter is Front_Inserter or Back_Inserter, you have discovered an interval of Insert as a priority policy. Interval delete. Each standard container provides an intervalted ERASE, but the sequence and the return type of the associated container are different. The sequence container provides this:

Iterator Container :: ERASE (Iterator BEGIN, ITERATOR END);

And related containers provide this:

Void Container :: ERASE (Iterator BEGIN, ITERATOR END);

Why is it different? The interpretation is if the ERASE's associated container version returns an iterator (the next element of the deleted element) will incur a decline in performance that cannot be accepted. I am a lot of people who have found this explanation of this person, but the standard is that the standard is said, and the standard said ERASE sequence and related container versions have different return types.

Most of this terms of performance analysis of INSERT can also be used in ERASE. The number of function calls for single-element deletion is still greater than the one-time call interval delete. When using a single element deletion, each element value must still move one of their destination, and the interval deletion can move them to the target location in a separate move.

One argument for the insertion and deletion of Vector and String is that there must be a lot of repetition assignments. (Of course, repeated recycling will occur.) This is because the memory automatic growth for Vector and String is adapted to new elements, but it does not automatically shrink when the number of elements is reduced. (Terms 17 describe how you reduce unnecessary memory held by Vector or String.)

A very important interval erase performance is Erase-Remove. You can learn about it in terms 32.

Interval assignment. Just as I mentioned at this clause, all standard column containers provide intervalous ASSIGN:

Void Container :: Assign (InputITerator Begin, InputITerator End);

So now we understand that try to use the interval member function to replace the three reliable arguments of the single element brother. The interval member functions are easier to write, and they understand your intentions more clearly, and they provide higher performance. It is a three-drive truck that is hard to defeat.

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

New Post(0)