• Effective C++ 笔记 —— Item 47: Use traits classes for information about types.


    There are five categories of iterators, corresponding to the operations they support:

    • Input iterators can move only forward, can move only one step at a time, can only read what they point to, and can read what they‘re pointing to only once. They’re modeled on the read pointer into an input file; the C++ library‘s istream_iterators are representative of this category.
    • Output iterators are analogous, but for output: they move only forward, move only one step at a time, can only write what they point to, and can write it only once. They're modeled on the write pointer into an output file; ostream_iterators epitomize this category.
    • Forward iterators can do everything input and output iterators can do, plus they can read or write what they point to more than once. This makes them viable for multi-pass algorithms. The STL offers no singly linked list, but some libraries offer one (usually called slist), and iterators into such containers are forward iterators. Iterators into TR1's hashed containers (see Item 54) may also be in the forward category.
    • Bidirectional iterators add to forward iterators the ability to move backward as well as forward. Iterators for the STL's list are in this category, as are iterators for set, multiset, map, and multimap.
    • Random access iterators add to bidirectional iterators the ability to perform "iterator arithmetic," i.e., to jump forward or backward an arbitrary distance in constant time. Such arithmetic is analogous to pointer arithmetic, which is not surprising, because random access iterators are modeled on built-in pointers, and built-in pointers can act as random access iterators. Iterators for vector, deque, and string are random access iterators.

    For each of the five iterator categories, C++ has a "tag struct" in the standard library that serves to identify it:

     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 among these structs are valid is-a relationships (see Item 32): it's true that all forward iterators are also input iterators, etc.

    The STL is primarily made up of templates for containers, iterators, and algorithms, but it also has a few utility templates. One of these is called advance. advance moves a specified iterator a specified distance:

     template<typename IterT, typename DistT>
     void advance(IterT& iter, DistT d); // move iter d units forward; if d < 0, move iter backward

    Conceptually, advance just does iter += d, but advance can't be implemented that way, because only random access iterators support the += operation. Less powerful iterator types have to implement advance by iteratively applying ++ or -- d times.

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

     template<typename IterT, typename DistT>
     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 calls to ++ or -- for other iterator categories
             else { while (d++) --iter; } //
         }
     }

    This requires being able to determine whether iter is a random access iterator, which in turn requires knowing whether its type, IterT, is a random access iterator type. In other words, we need to get some information about a type. That's what traits let you do: they allow you to get information about a type during compilation.

    Traits aren't a keyword or a predefined construct in C++; they're a technique and a convention followed by C++ programmers. One of the demands made on the technique is that it has to work as well for built-in types as it does for user-defined types. For example, if advance is called with a pointer (like a const char*) and an int, advance has to work, but that means that the traits technique must apply to built-in types like pointers.

    The fact that traits must work with built-in types means that things like nesting information inside types won't do, because there's no way to nest information inside pointers. The traits information for a type, then, must be external to the type. The standard technique is to put it into a template and one or more specializations of that template. For iterators, the template in the standard library is named iterator_traits:

     template<typename IterT> // template for information about iterator types
     struct iterator_traits;

    The way iterator_traits works is that for each type IterT, a typedef named iterator_category is declared in the struct iterator_traits. This typedef identifies the iterator category of IterT. 

    iterator_traits implements this in two parts. First, it imposes the requirement that any user-defined iterator type must contain a nested typedef named iterator_category that identifies the appropriate tag struct. deque's iterators are random access, for example, so a class for deque iterators would look something like this:

     template < /*...*/ > // template params elided
     class deque 
     {
     public:
         class iterator {
         public:
             typedef random_access_iterator_tag iterator_category;
             // ...
         };
         // ...
     };

    list's iterators are bidirectional, however, so they'd do things this way:

     template < /*...*/ >
     class list 
     {
     public:
         class iterator {
         public:
             typedef bidirectional_iterator_tag iterator_category; 
             // ...
         };
         // ...
     };

    iterator_traits just parrots back the iterator class's nested typedef:

    // 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 iterator_category;
         // ...
     };

    This works well for user-defined types, but it doesn't work at all for iterators that are pointers, because there's no such thing as a pointer with a nested typedef. The second part of the iterator_traits implementation handles iterators that are pointers.

    To support such iterators, iterator_traits offers a partial template specialization for pointer types. Pointers act as random access iterators, so that's the category iterator_traits specifies for them:

     template<typename T> // partial template specialization
     struct iterator_traits<T*> // for built-in pointer types
     {
         typedef random_access_iterator_tag iterator_category;
         // ...
     };

    At this point, you know how to design and implement a traits class:

    • Identify some information about types you’d like to make available (e.g., for iterators, their iterator category).
    • Choose a name to identify that information (e.g., iterator_category).
    • Provide a template and set of specializations (e.g., iterator_traits) that contain the information for the types you want to support.

    Given iterator_traits — actually std::iterator_traits, since it’s part of C++'s standard library — we can refine our pseudocode for advance:

     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 looks promising, it's not what we want. For one thing, it will lead to compilation problems, but we'l explore that in Item 48; right now, there's a more fundamental issue to consider. IterT's type is known during compilation, so iterator_traits::iterator_category can also be determined during compilation. Yet the if statement is evaluated at runtime (unless your optimizer is crafty enough to get rid of it). Why do something at runtime that we can do during compilation? It wastes time (literally), and it bloats our executable. 

    What we really want is a conditional construct (i.e., an if...else statement) for types that is evaluated during compilation. As it happens, C++ already has a way to get that behavior. It's called overloading.

    To get advance to behave the way we want, all we have to do is create multiple versions of an overloaded function containing the "guts" of advance, declaring each to take a different type of iterator_category object. I use the name doAdvance for these functions:

     // use this impl for random access iterators
     template<typename IterT, typename DistT> 
     void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag)
     {
         iter += d;
     }
    
     // use this impl for bidirectional iterators
     template<typename IterT, typename DistT> 
     void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag)
     {
         if (d >= 0) { while (d--) ++iter; }
         else { while (d++) --iter; }
     }
    
     // use this impl for input iterators
     template<typename IterT, typename DistT> 
     void doAdvance(IterT& iter, DistT d, std::input_iterator_tag)
     {
         if (d < 0) {
             throw std::out_of_range("Negative distance"); // see below
         }
         while (d--) ++iter;
     }

    Given the various overloads for doAdvance, all advance needs to do is call them, passing an extra object of the appropriate iterator category type so that the compiler will use overloading resolution to call the proper implementation:

     template<typename IterT, typename DistT>
     void advance(IterT& iter, DistT d)
     {
         // call the version of doAdvance that is appropriate for iter’s iterator
         doAdvance(iter, d, 
             typename 
             std::iterator_traits<IterT>::iterator_category() 
         ); 
     }

    We can now summarize how to use a traits class:

    • Create a set of overloaded "worker" functions or function templates (e.g., doAdvance) that differ in a traits parameter. Implement each function in accord with the traits information passed.
    • Create a "master" function or function template (e.g., advance) that calls the workers, passing information provided by a traits class.

    TR1 (see Item 54) introduces a slew of new traits classes that give information about types, including is_fundamental (whether T is a built-in type), is_array (whether T is an array type), and is_base_of<t1, t2=""> (whether T1 is the same as or is a base class of T2). All told, TR1 adds over 50 traits classes to standard C++.

    Things to Remember

    • Traits classes make information about types available during compilation. They're implemented using templates and template specializations.
    • In conjunction with overloading, traits classes make it possible to perform compile-time if...else tests on types.
  • 相关阅读:
    Fork/Join框架基本使用
    服务端高并发分布式架构演进之路
    Netty专题(一)-----计算机网络、TCP/ICP、linux网络I/O模型
    Nginx专题(四)-----https、nginx高可用
    Nginx专题(三)-----核心原理、跨域解决、防盗链、缓存以及压缩
    微信开放平台开发第三方授权登陆(四):微信小程序
    微信开放平台开发第三方授权登陆(三):微信公众号
    微信开放平台开发第三方授权登陆(二):PC网页端
    微信开放平台开发第三方授权登陆(一)-----开发前准备
    Mysql:如果数据存在则更新,不存在则插入
  • 原文地址:https://www.cnblogs.com/zoneofmine/p/15954010.html
Copyright © 2020-2023  润新知