Valid tive C ++, 3rd edition, item 47: Use traits classes (feature class) (lower) for type information)

Source: Internet
Author: User
Tags traits

(Click here, next to the previous article)

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:
TypedefRandom_access_iterator_tagIterator_category;
...
};
...
};

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

Template <...>
Class list {
Public:
Class iterator {
Public:
TypedefBidirectional_iterator_tagIterator_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 <typename itert>
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 providesPartial template Specialization(Some templates are special ). Pointers behavior is similar to random access iterators (Random Access iterator), so this is the type specified by iterator_traits:

Template <typename itert> // partial template Specialization
Struct iterator_traits <Itert *> // 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 to it (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) that 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 <typename itert, typename distt>
Void advance (itert & ITER, distt D)
{
If (Typeid(Typename STD: iterator_traits <itert >:: 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, but we will study it again at item 48. Now there is a more basic issue to be discussed. The itert type is known during compilation, so iterator_traits <itert>: 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 <typename itert, typename distt> // use this impl
Void doadvance (itert & ITER, distt D, // Random Access
STD: random_access_iterator_tag) // Iterators

{
ITER + = D;
}

Template <typename itert, typename distt> // 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 <typename itert, typename distt> // 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 <typename itert, typename distt>
Void advance (itert & ITER, distt D)
{
Doadvance(// Call the version
ITER, D, // of doadvance
Typename// That is
STD: iterator_traits <itert>: 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, in addition to iterator_category, four other pieces of information about iterators (iterator) are provided (the most common one is value_type -- Item 42, which demonstrates the example of using iterator_traits ). 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 (see item 54) introduces a large number of new traits classes to provide information about types, including is_fundamental <t> (whether T is a built-in type (built-in type )), is_array <t> (whether T is an array type (array type), and is_base_of <t1, t2> (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.

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.