C ++ proverbs: use feature classes for type information

Source: Internet
Author: User
C ++ tips: use feature classes for type information-general Linux technology-Linux programming and kernel information. The following is a detailed description. STL mainly consists of teminers (container), iterators (iterator) and templates (templates) of algorithms (algorithm), but there are also several utility templates (practical templates ). One of them is called advance. Advance moves a specified iterator (iterator) to a specified distance:

Template // move iter d units
Void advance (IterT & iter, DistT d); // forward; if d <0,
// Move iter backward

In terms of concept, advance is only engaged in iter + = d, but advance cannot be implemented in this way, because only random access iterators (random access iterator) supports + = operation. The iterator type is not powerful enough to implement advance by repeatedly using ++ or -- d.

Do you remember STL iterator categories? No problem. Let's make a simple review. Five types of iterators correspond to the operations they support ). The input iterators (input iterator) can only move forward and can only move one step at a time. It can only read what they point to and can only read once. They are based on read pointer (read pointer) in an input file. istream_iterators in the C ++ library is a typical example of this type. Output iterators (output iterator) is similar to this one, but used for output: they can only move forward, only one step at a time, only write what they point to, and can only write once. They are based on the write pointer (write pointer) in an output file. ostream_iterators is a typical example of this type. This is the two least powerful iterator categories (iterator type ). Because input and output iterators can only move forward and can only read or write at most once where they direct, they are only applicable to one-pass operations.

A more powerful iterator category (iterator type) is forward iterators (forward iterator ). This iterators can do everything that input and output iterators can do, plus, they can read or write what they point to more than once. This allows them to be used for multi-pass operations. STL does not provide singly linked list (one-way linked list), but some libraries provide (usually called slist) iterators (iterators) for such containers (containers) that is, forward iterators (forward iterator ). The iterators (iterator) of hashed containers (hash container) of TR1 can also belong to forward category (forward iterator ).

Bidirectional iterators (bidirectional iterator) adds the same backward movement capability as forward iterators (forward iterator. Iterators (iterator) of STL list belong to this type. The iterators (iterator) of set, multiset, map and multimap are also the same.

The most powerful iterator category (iterator type) is random access iterators (random access iterator ). This iterators (iterator) adds the "iterator arithmetic" ("iterator operation") capability to bidirectional iterators (bidirectional iterator), that is, jump forward or backward to any distance in constant time. This operation is similar to pointer operations, which is not surprising, because random access iterators (random access iterator) is prototype based on built-in pointers (built-in pointer, built-in pointers (built-in pointer) can act the same way as random access iterators (random access iterator. The iterators (iterator) of vector, deque, and string are random access iterators (random access iterator ).

For each of the five iterator categories, C ++ has a "tag struct" ("tag struct") used to identify it in the Standard Library:

Struct input_iterator_tag {};

Struct output_iterator_tag {};

Struct forward_iterator_tag: public input_iterator_tag {};

Struct bidirectional_iterator_tag: public forward_iterator_tag {};

Struct random_access_iterator_tag: public bidirectional_iterator_tag {};

The inheritance relationships between these structs are valid is-a relationships: All forward iterators (forward iterators) are also input iterators (input iterators), and so on, this is true. We will soon see the functions of this inheritance (inheritance.

But return to advance. One way to implement advance for different iterator capabilities is to use lowest-common-denominator that repeatedly increases or reduces iterator loops (least common feature) policy. However, this method takes linear time (linear time ). Random access iterators (random access iterator) supports constant-time iterator arithmetic (constant time iterator operation). When it appears, we 'd better use this capability.

What we really want to do is implement advance like this:

Template
Void advance (IterT & iter, DistT d)
{
If (iter is a random access iterator ){
Iter + = d; // use iterator arithmetic
} // For random access iters
Else {
If (d> = 0) {while (d --) ++ iter;} // use iterative callto
Else {while (d ++) -- iter;} // ++ or -- for other
} // Iterator categories
}

This requires you to determine whether the iter is a random access iterators (random access iterator). In turn, you need to know its type, IterT, whether it is a random access iterators (random access iterator) type. In other words, we need to get some information about a type. This is what traits allows you to do: they allow you to get information about a type during compilation. Traits is not a keyword or pre-defined structure in C ++; they are a technique followed by C ++ programmers. One of the requirements for building this technology is that it must be as effective on built-in types (built-in type) as on user-defined types (user-defined type. For example, if advance is called by a pointer (such as a const char *) and an int, advance must be valid, however, this means that the traits technology must be applicable to built-in types (built-in types) Like pointers ).

The fact that traits must be valid for built-in types (built-in type) means that it is not possible to embed information into the type, because there is no way to embed information into the pointer. Therefore, the traits information of a type must be outside the type. The standard method is to place it in one or more specializations of the template and the template. For iterators, the template in the standard library is called iterator_traits:

Template // template for information about
Struct iterator_traits; // iterator types

As you can see, iterator_traits is a struct (struct ). By convention, traits is always implemented as a struct (struct ). Another practice is to implement the structs (struct) of traits, known as traits classes (which is not fabricated by me.

Iterator_traits is used to declare a typedef named iterator_category in struct (struct) iterator_traits for each IterT type. This typedef is considered IterT iterator category (iterator type ).

Iterator_traits implements this in two parts. First, it requires that any user-defined iterator (user-defined iterator) type must contain a nested typedef named iterator_category to identify the appropriate tag struct (tag struct ). For example, deque iterators (iterator) is randomly accessed, so the class of a deque iterators looks like this:

Template <...> // template params elided
Class deque {
Public:
Class iterator {
Public:
Typedef random_access_iterator_tag iterator_category;
...
};
...
};

However, list iterators are bidirectional, so they do this:

Template <...>
Class list {
Public:
Class iterator {
Public:
Typedef bidirectional_iterator_tag iterator_category;
...
};
...
};
Iterator_traits simply imitates the nested typedef of the iterator class:

// The iterator_category for type IterT is whatever IterT says it is;
// See Item 42 for info on the use of "typedef typename"
Template
Struct iterator_traits {
Typedef typename IterT: iterator_category;
...
};

In this way, user-defined types (user-defined type) can run well. However, iterators, which itself is a pointers (pointer), does not work because there is nothing similar to a pointer with a nested typedef. The second part of iterator_traits is the iterators (iterator) of pointers ).

To support such iterators (iterator), iterator_traits provides a partial template specialization for pointer types (pointer type ). Pointers behavior is similar to random access iterators (random access iterator), so this is the type specified by iterator_traits:

Template // partial template specialization
Struct iterator_traits // for built-in pointer types
{
Typedef random_access_iterator_tag iterator_category;
...
};

So far, you have learned how to design and implement a traits class:

· Identify information about types that you want to make available (for example, iterators are their iterator category )).

· Select a name to identify this information (for example, iterator_category ).

· Provides a template and a series of specializations (special) (for example, iterator_traits), which contain information about the types you want to support.

Given iterator_traits-actually std: iterator_traits, because it is part of the C ++ standard library-we can improve our advance pseudocode:

Template
Void advance (IterT & iter, DistT d)
{
If (typeid (typename std: iterator_traits: iterator_category) =
Typeid (std: random_access_iterator_tag ))
...
}

Although this seems a little hopeful, it is not what we want. In a certain state, it will cause compilation problems. We will study it later. Now, there is a more basic issue to be discussed. The IterT type is known during compilation, so iterator_traits: iterator_category can be determined during compilation. However, if statements can be evaluated only when they are running. Why do we need to do what we can do during compilation at runtime? It wastes time (strictly speaking) and expands our Execution Code.

What we really want is a conditional construct (condition structure) (that is, an if... else statement) that is identified during compilation ). It happens that C ++ already has a way to get this behavior. It is called overloading (overload ).

When you reload a function f, you specify different parameter types for different overloads (reloads ). When you call f, the compiler will pick out the best overload (overload) based on the passed arguments (real parameters ). Basically, the compiler will say, "If this overload (overload) is the best match with the thing to be passed, call this f. If another overload (overload) is the best match, call it. If the third overload (overload) is the best, call it "and so on. Have you seen it? A compile-time conditional construct (condition structure during compilation) for the type ). To make advance have the behavior we want, all we have to do is create multiple versions of the overloaded function that contains the "content" of advance (the original article is incorrect here, modify according to the author's website | Translator's note) and declare that they are of different iterator_category object types. I use doAdvance for these functions:

Template // use this impl
Void doAdvance (IterT & iter, DistT d, // random access
Std: random_access_iterator_tag) // iterators
{
Iter + = d;
}

Template // use this impl
Void doAdvance (IterT & iter, DistT d, // bidirectional
Std: bidirectional_iterator_tag) // iterators
{
If (d> = 0) {while (d --) ++ iter ;}
Else {while (d ++) -- iter ;}
}

Template // use this impl
Void doAdvance (IterT & iter, DistT d, // input iterators
Std: input_iterator_tag)
{
If (d <0 ){
Throw std: out_of_range ("Negative distance"); // see below
}
While (d --) ++ iter;
}

Because forward_iterator_tag inherits from input_iterator_tag, The doAdvance version for input_iterator_tag will also process forward iterators (forward iterator ). This is the motivation for inheritance between different iterator_tag structs. (In fact, this is part of the motivation for all public inheritance (public inheritance): code written for base class types (base class type) can also be used for derived class types (derived class type) it works .)

The advance specification allows positive and negative moving distances between random access (random access) and bidirectional iterators (bidirectional iterator), but if you try to move a forward) or input iterator (input iterator) is a negative distance, the behavior is undefined. In the implementation I have checked, it is simply assumed that d is non-negative. Therefore, if a negative distance is input, a very long cycle is entered until the count is reduced to zero. In the above code, I show that an exception is thrown. Both implementations are correct. The curse of undefined behavior is: you cannot predict what will happen.

Provides various reloads for doAdvance. All advance needs to do is call them and pass an additional object of the appropriate iterator category (iterator type) type so that the compiler can use overloading resolution (reload parsing) to call the correct implementation:

Template
Void advance (IterT & iter, DistT d)
{
DoAdvance (// call the version
Iter, d, // of doAdvance
Typename // that is
Std: iterator_traits: iterator_category () // appropriate
); // Iter's iterator
} // Category

We can now outline how to use a traits class:

· Create a set of overloaded "worker" functions (function) or function templates (function template) (for example, doAdvance), which are different on a traits parameter (parameter. Implement each function in the same way as the transmitted traits information.

· Create a "master" function (function) or function templates (function template) (for example, advance) to call these workers and pass the information provided by a traits class.

Traits is widely used in standard libraries. Iterator_traits is available. Of course, with iterator_category, four other pieces of information about iterators are provided (value_type is the most commonly used ). In addition, char_traits holds information about character types (character type) and numeric_limits provides information about numeric types (value type), such as the minimum and maximum values of values. (The name numeric_limits is a bit strange, because the more common convention about traits classes ends with "traits", but it is called numeric_limits, so numeric_limits is our name .)

TR1 introduces a large number of new traits classes to provide information about types, including is_fundamental (whether T is a built-in type (built-in type )), is_array (whether T is an array type (array type), and is_base_of (whether T1 is the same as T2 or a base class (base class) of T2 )). In total, TR1 has added more than 50 traits classes to the Standard C ++.

Things to Remember

· Traits classes make information about types available during compilation. They are implemented using templates and template specializations.

· Combined with overloading (overload), traits classes makes it possible to execute the if... else test of the compilation period type.
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.