Reading Notes Objective c ++ Item 47 uses traits class to indicate the type information. Optional tivetraits
STL mainly consists of templates created for containers, iterators, and algorithms, but it also has some function templates. One of them is advance. Advance moves a specified iterator to the specified distance:
1 template<typename IterT, typename DistT> // move iter d units2 void advance(IterT& iter, DistT d); // forward; if d < 0,3 // move iter backward
In terms of concept, advance only implements iter + = d, but advance is not implemented in this way, because only the random access iterator supports the + = operation. Other weaker iterator types must be implemented through repeated ++ or -- d times.
1. Review of five types of iterators
Do you not remember the types of STL iterators? No problem. Now let's make some simple review. There are 5 types of iterators in total, corresponding to the five operations they support.Input iterator (Input iterator)You can only move forward. You can only move one step at a time. You can only read the content pointed to by them once. This iterator simulates the read pointer of the input file; istream_iterator of the c ++ library represents this iterator.Output iterator (Outputiterator)Similar to the input iterator, but for the output iterator, they can only move forward and only move one step at a time. They can only write to the memory they point to and can only write once. They simulate the write pointer of the output file; ostream_iterator represents this iterator. This is the weakest iterator in two types of functions. Because the input and output iterators can only move forward, they can only read and write the content they point to at most once, they can only be used by the one-pass algorithm.
A more powerful iterator category consistsForward iterator (Forwarditerator). Such iterators can do everything Input and Output iterators can do, and they can read and write the content they point to multiple times. This allows them to be used by the multi-pass algorithm. STL does not provide a single-chain table, but some libraries provide (usually called slist). The iterator in this container is a forward iterator. The hash container (Item 54) iterator in TR1 may also be a forward iterator.
Bidirectional iterator (Bidirectionaliterators)Compared with the forward iterator, the backward movement capability is added. The iterator provided for the list in STL belongs to this category. The iterator provided for set, multiset, map, and multimap are also of this category.
The most powerful iterator category isRandom Access iterator (Randomaccess iterator). Compared with the two-way iterator, this type of iterator adds the ability to execute the iterator arithmetic operation, that is, to jump any distance forward or backward within the constant time. This operation is similar to pointer operations. Don't be surprised, because the random access iterator simulates the built-in type pointer, and the behavior of the built-in type pointer is like the random access iterator. Vector, deque, and string iterators are all random access iterators.
To identify these five types of iterators, C ++ provides a "tag struct" for the five types of iterators in the Standard Library ":
1 struct input_iterator_tag {};2 struct output_iterator_tag {};3 struct forward_iterator_tag: public input_iterator_tag {};4 struct bidirectional_iterator_tag: public forward_iterator_tag {};5 struct random_access_iterator_tag: public bidirectional_iterator_tag {};
The inheritance relationships between these structs are valid "is-a" relationships (Item32): All forward iterators are also input iterators, and so on. We will soon see the utility of this inheritance.
2. How to Implement advance
Return to advance. Considering different iterator functions, one way to achieve advance is to use the minimum public denominator policy of the Loop:Repeatedly add or subtract the iterator. However, this method will costLinear Time. Random Access iterator supportConstant timeThe iterator algorithm will use it when we need it.
What we really want is to implement advance as follows:
1 template<typename IterT, typename DistT> 2 void advance(IterT& iter, DistT d) 3 { 4 if (iter is a random access iterator) { 5 6 iter += d; // use iterator arithmetic 7 8 } // for random access iters 9 10 else { 11 12 13 if (d >= 0) { while (d--) ++iter; } // use iterative calls to14 else { while (d++) --iter; } // ++ or -- for other15 } // iterator categories16 }
This requires determining whether the iter is a random access iterator, that is, whether it is of the type IterT or random access iterator. In other words, we need to obtain information about a type. This is what trait asked you to do:They allow you to obtain information about a type during compilation..
3. Traits Technical Analysis 3.1 requirements for using traits Technology
Traits is not a keyword in C ++ or a predefined concept. They are a technology and a Convention that must be followed by C ++ programmers.One requirement for using this technology is that it must make the built-in type work as well as the user-defined type.. For example, if the advance input parameter is a pointer (like constchar *) and an Int, advance must be able to work, however, this means that the trait technology must be applicable to built-in types like pointers.
Traits must be able to work with built-in types, which means that some information cannot be embedded within the type, because there is no way to embed information inside the pointer. Therefore, a type of traits information must be placed outside the type. The standard technology is to place it in one or more special instances of templates and templates. For the iterator, the template in the standard library is named iterator_traits:
1 template<typename IterT> // template for information about2 struct iterator_traits; // iterator types
As you can see, iterator_traits is a struct. By convention, traits is often implemented as a struct. Another common method is to replace the struct that implements traits with the traits class (this is not what I said ).
Iterator_traits is used to declare a typedef called iterator_category in iterator_traits <IterT>. This typedef uniquely identifies the IterT iterator category.
3.2 To implement traits class, you need to process user-defined types.
Iterator_traits will implement it in two parts. First, it forces any user-defined iterator type to contain an embedded typedef called iterator_category, which can recognize the appropriate tag struct. For example, the deque iterator is randomly accessed, so the class of a deque iterator will look like the following:
1 template < ... > // template params elided 2 3 class deque { 4 5 public: 6 7 class iterator { 8 9 public: 10 11 typedef random_access_iterator_tag iterator_category; 12 13 ... 14 15 }; 16 17 ... 18 19 };
The List iterator is bidirectional, so it is processed in the following way:
1 template < ... > 2 3 class list { 4 5 public: 6 7 class iterator { 8 9 public:10 11 typedef bidirectional_iterator_tag iterator_category;12 13 ...14 15 };16 17 ...18 19 };
Iterator_traits only reuses the built-in typedef of the iterator class:
1 // the iterator_category for type IterT is whatever IterT says it is; 2 3 // see Item 42 for info on the use of “typedef typename” 4 5 template<typename IterT> 6 7 struct iterator_traits { 8 9 typedef typename IterT::iterator_category iterator_category;10 11 ...12 13 };
3.3 to implement traits class, you need to process the pointer type
This works well for user-defined types, but it does not work for the pointer iterator because the pointer does not contain the nested typedef. The second part of Iterator_trait implementation needs to process the pointer iterator.
To support this iterator, iterator_traits provides a partial template (partial template specialization) for the pointer type ). The behavior of pointers is similar to that of random access iterators, so iterator_trait specifies Random Access categories for them:
1 template<typename T> // partial template specialization2 struct iterator_traits<T*> // for built-in pointer types3 {4 typedef random_access_iterator_tag iterator_category;5 ...6 };
3.4 Summary of implementing traits class
Now you have learned how to design and implement a traits class:
- Confirm some information about the type you want to support (for example, their iterator category for the iterator ).
- To confirm the information, you need to select a name (for example, iterator_category)
- The type you want to support provides a template containing the relevant information and some special features (for example, iterator_traits)
4. Use traitsclass to enable advance4.1 class judgment should not be performed at runtime
Consider iterator_traits, which is actually std: iterator_traits. Since it is part of the C ++ standard library, we can refine the implementation of advance into our own pseudo code:
1 template<typename IterT, typename DistT>2 void advance(IterT& iter, DistT d)3 {4 if (typeid(typename std::iterator_traits<IterT>::iterator_category) ==5 typeid(std::random_access_iterator_tag))6 ...7 }
Although this seems promising, it will not be as expected. First, it will cause compilation problems, which we will study in Item 48; now, there are more basic problems to consider. The IterT type is confirmed during compilation, so iterator_traits <IterT>: iterator_category can also be determined during compilation. However, the if statement will be evaluated at runtime (unless your optimizer is crazy enough to remove the if Statement ). Why do I move the tasks that can be done during the compilation period to the runtime? It will waste time and cause the Execution Code to expand.
4.2 advance the condition evaluation to the compilation period-use overload
What we really want is to provide a conditional structure (that is, an if... Else Statement ). C ++ already has a way to implement this behavior. Her name isHeavy Load.
When you reload a function f, you specify different parameter types for different overload functions. When you call f, the compiler selects the best matching overload function based on the parameters you pass. The compiler will say: "If this overload is the best match for the passed parameter, call this f. If another overload function is the best match, call another function; if the third function is the best match, call the third function, and so on. Have you seen it? This is a type-related compile-time condition structure. In order for advance to show what we want, all we have to do is create multiple versions of an overloaded function, which contains the "guts" of advance ", each function has a different type of iterator_category object. I name these functions doAdvance:
1 template<typename IterT, typename DistT> // use this impl for 2 void doAdvance(IterT& iter, DistT d, // random access 3 std::random_access_iterator_tag) // iterators 4 { 5 iter += d; 6 } 7 template<typename IterT, typename DistT> // use this impl for 8 void doAdvance(IterT& iter, DistT d, // bidirectional 9 std::bidirectional_iterator_tag) // iterators10 {11 if (d >= 0) { while (d--) ++iter; }12 else { while (d++) --iter; }13 }14 template<typename IterT, typename DistT> // use this impl for15 void doAdvance(IterT& iter, DistT d, // input iterators16 std::input_iterator_tag)17 {18 if (d < 0 ) {19 throw std::out_of_range("Negative distance"); // see below20 }21 while (d--) ++iter;22 }
Because forward_iterator_tag inherits from input_iterator_tag, The doAdvance version provided for input_iterator_tag can also process the forward iterator. This is the motivation for introducing inheritance between different iterator_tag struct. (In fact, this is also all motivation for using public inheritance: The Code implemented for the base class type is also applicable to the type of the derived class .)
For random access iterators and two-way iterators, the special version of advance can simultaneously move forward or negative, but for the forward iterator or input iterator, if you want to perform a negative movement, undefined behaviors will occur. In implementation, if we simply assume that d is not negative, when a negative parameter is passed, you will enter a long loop until d changes to 0. In the above Code, the alternative method I used is to throw an exception. Both implementations are effective. This is the curse of undefined behavior: you cannot predict what will happen.
Consider the different versions that doAdvance loads. All advance needs to do is call them and pass an additional appropriate iterator category object, finally, the compiler can use the overload solution to call the appropriate implementation:
1 template<typename IterT, typename DistT> 2 void advance(IterT& iter, DistT d) 3 { 4 doAdvance( // call the version 5 iter, d, // of doAdvance 6 typename // that is 7 std::iterator_traits<IterT>::iterator_category() // appropriate for 8 ); // iter’s iterator 9 10 } // category
5. traits class usage Summary
We can summarize how to use traits class:
- Create a series of overloaded "worker" functions or function templates (for example, doAdvance), which are differentiated by using the traits parameter. Each function is implemented based on the transmitted traits information.
- Create a "master" function or function template (for example, advance) to call worker and pass the information provided by the traits class.
Traits is widely used in the standard library. For iterator_traits, in addition to iterator_category, four other types of information are provided for the iterator (the most useful is an example given in value_type-Item 42 .) Char_traits stores the character type information. numeric_limits provides the numeric type information, such as the maximum and minimum values they can represent. (Numeric_limits may surprise you because the traditional naming method ends with "traits", but numeric_limits does not comply with it .)
TR1 (Item 54) introduces a large number of new traits classes to provide information for types, including is_fundamental <T> (judge whether T is a built-in type ), is_array <T> (determine whether T is an array), and is_base_of <T1, T2> (determine whether T1 and T2 are the same or the T2 base class ). TR1 adds about 50 traits classes to Standard C ++.
6. Summary of these terms
- Traits classes allow you to obtain type information during compilation. They are implemented using templates and Special versions of templates.
- Using overload, traits classes makes it possible to execute the if-else test during the compilation period on the type.