Effective STL Terms 30

zhaozj2021-02-11  209

Terms 30: Make sure the target interval is large enough

The STL container automatically expands them to accommodate new objects when they are added (via INSERT, PUSH_FRONT, PUSH_BACK, etc.). This work is very good, some programmers are paralyzed because of this faith, think they don't have to worry about vacating the objects in the container, because the container can take care of these. If so, just fine! The problem appeared when the programmer wanted to insert the object in the container but did not tell STL. This is a common method of self-performance:

Int Transmogrify (int x); // This function is from X

// Produce some new value

Vector values;

... // put the data into values

Vector results; // Applies Transmogrify to

TRANSFORM (Values.Begin (), Values.end (), // VALUES Each object

Results.end (), // Turn this VALUES

TRANSMOGRIFY); // Attached to Results

// This code has bugs!

In this example, the Transform is informative interval starting from Results.end (), so that it is where to start writing the results of Transmogrify on each element of VALUES. Just like all the algorithms that use the target interval, Transform writes the result by assigning the elements of the target interval, Transform is applied to Values ​​[0] and assigns * results.end (). Then it will apply Transmogrify to Value [1] and assign the results to * (results.end () 1). That can only bring disasters, because there is no object in * results.end () * (results.end () 1) is not! Calling Transform is incorrect because it assigns an object that does not exist. (Terms 50 explain how a debugging implementation of STL detects this problem in operation period.) Programmer who made this error almost always thought that the results of their call algorithm can be inserted into the target container. If that is what you want, you must say it. STL is a library, not a spirit. In this example, "Please put the result of Transform's results in the end of the RESULTS container" to call back_inserter to generate an iterator that specifies the starting point of the target interval:

Vector results; // Applies Transmogrify to

Values.Begin (), Values.end (), each object in // Values,

Back_INSERTER (RESULTS), // End of RESULTS

Transmogrify); // Insert the returned VALUES

Internally, the iterator returned by back_inserter calls PUSH_BACK, so you can use Back_Iserter on any container that provides PUSH_BACK (any standard sequence container: Vector, String, Deque, and List). If you want an algorithm to insert something on the front of the container, you can use Front_Inserter. Inside, Front_inserter uses PUSH_FRONT, so Front_Insert only and provides the container that provides that member function (which is devE and list):

... //

List results; // results is now list

Transform (Values.Begin (), VALUES.END (), // In the RESULTS front-end Front_insert (Results), //

Transmogrify); // Insert the result of Transform

Because Front_Insert is added with PUSH_FRONT to add each object to the object order in Results, in contrast of the corresponding object sequence in Values. This is also one of the reasons why Front_Inserter has no back_inserter. Another reason is that VECTOR does not provide PUSH_FRONT, so Front_INSERTER cannot be used for Vector.

If you want Transform to put the output result at the front end of the Results, you also want to output the corresponding object order in VALUES, as long as it is iterated in the opposite order. VALUES:

List results; //

Values.Rbegin (), Values.rend (), // at the front end of the Results

Front_inserter (Results), // Insert Transform

Transmogrify); / / Keep the relative object order

Front_inserter allows you to force the algorithm into the results of the front end of the container, back_inserter lets you tell them to put the result in the backend of the container, a bit amazing thing is that INSERTER allows you to force the algorithm to insert their results into any location in the container:

Vector values; //

...

Vector results; //, in addition to now

... // Before calling Transform

// Results already have some data

Transform (Values.Begin (), Values.end (), // Putting Transmogrify

Inserter (Results, Results.begin () Results.Size () / 2), / / ​​Result Insert

TRANSMOGRIFY); // Results

Whether you use Back_Inserter, Front_Inserter or Insert, each time the insertion of the destination interval completes an object. Terms 5 explains that this may be expensive for continuous memory containers (VECTOR, STRING, and Deque), but the recommendations of Terms 5 (using interval member functions) cannot be applied to the case where the algorithm is used to complete the insert. In this case, Transform will write a value each time you write, you can't change.

When the container you want to be inserted is a Vector or String, you can minimize this cost by the recommendations of the Terms 14, pre-calling Reserve. You still have to withstand the overhead of moving elements every time, but at least you avoid the intrinsic memory of the reassigned container:

Vector values; //

Vector Results;

...

Results.Rserve (Results.Size () VALUES.SIZE ()); / / Determine Results at least

/ / Can also be installed

// VALUES.SIZE () Element

VALUES.EGIN (), Values.end (), //

INSERTER (RESULTS.SIZE () RESULTS.SIZE () / 2), //, RESULTS

TRANSMOGRIFY); / / no redistribution operation

When using RESERVE to increase the efficiency of a series of inserts, always remember that the RESERVE only increases the capacity of the container: The size of the container still has not changed. Even if you call RESERVE, when you want the container to add new elements to Vector or String, you must also use insert iterators (such as one of the iterators returned from back_inserter, front_inserter or inserter). It is necessary to make these very clearly, here there is an error method for increasing the efficiency of the example at the beginning of this Territory (that is, the results of the data of the data in the values ​​of the values ​​to Results):

Vector values; //

Vector Results;

...

Results.Rserve (results.size () value ()); //

Transform (Values.Begin (), Values.end (), // Write the result of Transmogrify

Results.end (), // Directation of memory

Transmogrify); // Behavior is undefined!

In this code, Transform happily attempts to assign the original and unmelted memory assignment of the RESULTS tail. Typically, this will result in an operation error because it makes sense only between the two objects, not between an object and the original bits. Even if this code happens to do things you want to do, RESULTS will not know that Transform is "created" on its unused capacity. It is still the same as the original when recalling Transform until the resultS knows. Similarly, its END iterator still points to the same location before transferring Transform. Conclusion? Using Reserve without using an inserted iterator, it will cause undefined behavior inside the algorithm, and it will also disappear.

The method of correctly writing this example is to use Reserve and insert iterators:

Vector values; //

Vector Results;

Results.Rserve (results.size () value ()); //

Transform (Values.Begin (), Values.end (), // Put the results of Transmogrify

Back_inserter (results), // Write the end of Results,

Transmogrify); // Avoid redistribution when processing

So far, I have assumed that you let the algorithms like Transform as new elements into the container. This is the usual expectations, but sometimes you have to cover the elements of the existing container rather than inserting new. When this situation, you don't need to insert iterators, but you still need to make sure your destination is large enough in accordance with the recommendations of this Terms.

For example, suppose you make Transform to override the Elements of Results. It is simple if it has the same elements as Values ​​as Values. If not, you must also use resize to make sure it has.

Vector values;

Vector Results;

...

if (Results.Size ()

Results.resize (Values.size ()); // and values

}

Transform (Values.Begin (), Values.end (), // overrides Values.Size ()

Elements of Results.begin (), // Results

Transmogrify);

Or you can empty Results and use insert iterators in the usual way:

...

Results.clear (); // Destroy Results

// All elements

Results.reserve (Values.size ()); // Keep enough space

Values.Begin (), Values.end (), // returns the TRANSFORM

Pack_INSERTER (RESULTS), // Add RESULTS

Transmogrify);

This Terms argue a lot of changes in this topic, but I hope that you can keep in mind. Whenever you use a algorithm that requires a designated destination interval, make sure the destination interval is large enough or can increase when the algorithm is executed. If you choose to increase the size, use the insert iterator, such as ostream_iterators, or returned from back_inserter, front_inserter, or inserter. This is something you need to remember.

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

New Post(0)