Effective STL Terms 2

zhaozj2021-02-11  227

Terms 2: Careful to "container unrelated code" fantasy

STL is based on generics. The array is generally used as a container, parameterized the type of object contained. The function is flushing into an algorithm, and the type of iterator used is parameterized. The pointer is extension to iterators, parameterized the type of object pointed to.

This is just a start. The independent container type is generally used as sequence or associated container, and similar containers have similar functions. Standard memory neighboring containers (see Terms 1) provide random access iterators, standard node-based containers (see clause 1) provide a bidirectional iterator. The sequence container supports PUSH_FRONT or PUSH_BACK, but the associated container is not supported. The associated container provides logs of Lower_Bound, Upper_Bound, and Equal_Range member functions of logarithmic time complexity, but the sequence container is not.

As the generalization continues, you will naturally want to join this movement. This approach is worthy of praise, and when you write your own container, iterator and algorithm, you will naturally push it. Hey, many programmers try to put it into different styles. They tried to differ in their software in their software, rather than the particularity programming of the container, so that they can be used, can be said, now is a vector, but will still be used in the future, such as deque or List. - You can use it without changing the code. In other words, they work hard to write "container unrelated code". This possibility is for good intention, but almost always causing trouble.

The enthusiastic "container-independent code" will soon find that there is no significance of writing the code that is necessary to work with the sequence container and associated container. Many member functions exist only in one of the containers, for example, only the sequence container supports PUSH_FRONT or PUSH_BACK, only the associated container supports count and lors_bound, and so on. In different types, even some simple operations such as INSERT and ERASE are also in the name and semantic. For example, when you insert an object into a sequence container, it remains in the location you place. However, if you insert an object into an associated container, the container moves this object to its location in the arrangement order. To mention another example, use an iterator as a parameter on a sequence container to call ERASE, it will return a new iterator, but nothing is returned on the associated container. (Terms 9 give an example to demonstrate this impact on the code you wrote.)

Assuming, then you want to write a code that can be used on all common sequence containers --Vector, Deque, and List. Obviously, you must use the intersection of their capabilities to write, which means you can't use RESERVE or CAPACITY (see Terms 14), because Deque and List do not support them. Due to the presence of List, you have to give up Operator [], and you must be limited to the performance of the bidirectional iterator. This means that you cannot use algorithms that need to be random access to iterators, including sort, stable_sort, partial_sort, and nth_element (see Terms 31).

On the other hand, you are eager to support the rules of the Vector, do not use push_front and pop_front, and use Vector and Deque to completely fail the joint method and the Sort of the member function. Under the above constraint, the latter means you can't adjust any of your "generalized sequence containers".

This is obvious. If you have any restrictions, your code will compile errors when you work at at least one container you want to use. It can be seen that this code has a lot of danger.

The culprit here is the different iterator, pointer, and reference for different sequence containers. To write the code that can correctly and the VECTOR, Deque, and List, you must assume that any operators that make those containers, pointers, or reference fails really work on your container. Therefore, you must assume that everything is invalidated every time, because Deque :: INSERT will fail all the iterator, and because of the lack of Capacity, Vector :: INSERT must also assume that all pointers and references are invalid. (Terms 1 explain Deque is the only reason why a pointer and reference still valid in the case of making it iteration, which can introduce a conclusion, all the call to ERASE must assume that all things have been invalidated. Want to know more? You can't pass the data in the container to the C style interface because only Vector supports this (see Terms 16). You can't use Bool as a saved object to instantiate your container because - as elaborated by Terms 18 - VECTOR

Not always as a vector, in fact it does not really save the BOOL value. You can't expect to enjoy the insertion and deletion of the constant time complexity of the List, because the insertion and deletion of the Vector and Deque are linear time complexity.

When these are all said, you only have a "generalized sequence container", you can't call RESERVE, CAPACITY, OPERATOR [], PUSH_FRONT, POP_FRONT, SPLICE, or any algorithm that needs to be randomly access iter; call INSERT and ERASE will have linear time complexity and invalid all iterators, pointers, and references; and cannot be compatible with C-style interfaces, BOOL cannot be stored. Is this really the container you want to use in your program? I don't think it.

If you control your ambition, decide to give up support for List, you still give up RESERVE, CAPACITY, PUSH_FRONT, and POP_FRONT; you still have to assume that all the calls of INSERT and ERASE have linear time complexity and will make all Things fail; you still can't comply with C style layout; and you still can't store BOOL.

If you give up the sequence container, change the code to only with different related containers, this situation has not improved. It is almost impossible to simultaneously compatibility with SET and MAP, because SET saves a single object, while the Map saves object pairs. It is also difficult to even compatible with Set and MultiSet (or MAP and MultiMap). The set / map of the INSERT member function returns only one value, and the return type of their Multi brothers is different, and you must avoid any assumptions for a copy of the value saved in the container. For MAP and MultiMap, you must avoid using Operator [], because this member function exists only in MAP.

Face fact: This is not necessary. Different containers are different, and their advantages and disadvantages have some significant difference. They are not designed to be interchangeable, and you can't do what packaging work. If you want to try it, you just have a destiny, but fate does not want to be tested.

Then, when you are black, you realize that you decide to use the container, um, not the ideal, and you need to use a different container type. You now know when you change the type of container, not just to correct the problem diagnosed by the compiler, and check all the code that uses the container, see those in accordance with the performance characteristics of the new container and the failover rules of the iterator, pointer, and reference. Need to modify it. If you switch from a vector to other things, you also need to confirm that you no longer rely on the Vector's C-compatible memory layout; if you are switching to a vector, you need to guarantee that you don't need it to save BOOL. Since there is an inevitability of changing the type of container once, you can use this common way to change to simplify: Encapsulating, package, and then encapsulation. One of the simplest methods is to use TypedEf to the container and iterator types by freely. So don't write this

Class widget {...};

Vector VW;

Widget bestwidget;

... // Give your bestwidget a value

Vector :: item i = // Look for Widgets with BestWidget

Find (vw.begin (), vw.end (), bestwidget;

To write this:

Class widget {...};

Typedef Vector widgetContainer;

Typedef WidgetContainer :: item wciterator;

WidgetContainer VW;

Widget bestwidget;

...

Wciterator i = find (vw.begin (), vw.end (), bestwidget);

This is to change the type of container to make much easier. If the problem is changed, it is particularly convenient to add the user's allocator. (A change that does not affect the failover rule of iterator / pointer / reference)

Class widget {...};

Template / / About Why is a Template

SpecialalLocator {...}; // See Terms 10

TypedEf Vector > WidgetContainer;

Typedef WidgetContainer :: item wciterator;

WidgetContainer vw; // can still be used

Widget bestwidget;

...

Wciterator i = find (vw.begin (), vw.end (), bestwidget); // can still be used

If you don't have any meaning of code packages from Typedef, you will still praise them to save a lot of work. For example, you have a type of object as follows

MAP

VectorWidget> :: item,

CiStringCompare> // cistringcompare is "String Comparison of Ignore Size"

// Refer to Terms 19

And you have to use const_iterator to traverse this map, you really want to write down once

Map :: item, cistringcompare> :: const_iterator

? When you use STL for a while, you will realize TypeDef is your good friend.

Typedef is just other types of synonymous words, so it provides a pure lexic (translation: unlike #define is replaced in the precompilation phase). Typedef does not prevent users from using (or dependent) any (or dependent) they should not be used. If you don't want to expose the user's type of container for you, you need to be more fire, that is, Class. To restrict if another container may need to modify the code with a container type, you need to hide the container in the class, and to limit the number of special information visibility of the container through the class interface. For example, if you need to create a list of customers, please do not use List directly. Instead, create a CustomerList class, hide List in its private area:

Class customerlist {

Private:

Typedef List CustomerContainer;

Typedef CustomerContainer :: item CCITERATOR;

CustomerContainer Customers;

Public: // passed this interface

... // Restrict the visibility of special information of LIST

}

First, this may be a bit boring. After all, a Customer List is a list, right? Oh, it may be. Later, you may find that inserting and deleting customers from the middle of the list is not as frequent as you think, but you really need to quickly determine 20% of the top of the customer list - a nth_element algorithm special task (see Terms 31). But nth_element needs to be randomly access iterators and cannot be compatible with LIST. In this case, your customer "list" may be more implemented with vector or degque.

When you decide to make this change, you still have to check the member functions of each CustomerList and each friend, see the extent they are affected (according to the performance and iterator / pointer / reference failure, etc.), But if you do a good job in making details to CustomerList, the impact of customers who are CustomerList will be small. You can't write an independent code, but they may be.

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

New Post(0)