Algorithm Library Design in C ++ II (STL and generic programming)

Source: Internet
Author: User
3. STL and generic programming
    • Introduction
The STL programming mode is generic programming, which has the following core ideas:
  • ForAlgorithmThe assumption of as few data types as possible, and vice versa, so that algorithms and data can collaborate as well as possible.

Expressing algorithms with minimal assumptions about data processing actions, and vice versa,

Thus making them as interoperable as possible.

  • Generalize the algorithm as much as possible without losing the running efficiency. That is, the efficiency of the generalized algorithm is the same as that of the original algorithm.

Lifting of a concrete algorithm to as general a level as possible without losing efficiency;

I. e., the most abstract form such that when specialized back to the concrete case

The result is just as efficient as the original algorithm.

  • When the upward generalization cannot meet all the algorithm requirements, a more general version is proposed, but the most efficient version can be automatically called when there are specific requirements.
  • Providing more than one generic Algorithm for the same purposeAnd at the same level of each action,

When none dominates the others in efficiency for all inputs.

This introduces the necessity to provide sufficiently precise characterizations of the domain for which each algorithm is the most efficient.

    • Concepts and models
Swap Functions
Template <class T> void swap (T & A, T & B) {t tmp = A; A = B; B = TMP ;}
When swap is executed and instantiated, the template parameter (place holder) T is changed to an actual parameter type. However, the premise of successful compilation is that this real parameter supports copy construction and assignment operations.
Note that the compiler only performs a syntax check. Is the semantics of your copy constructor correct?ProgramEnsure by yourself.
We call a series of requirement sets a concept concep. If a real parameter meets all the requirements of a concept, requirments is called a model for the concept.
In the swap example, Int Is a reproducible concept model.IntIs a model of the ConceptAssignable.
Common Basic Concepts

 

 

Compared with the object-oriented model, the concept conepts analogy and virtual base class (Interface), model analogy and sub-class dervied classes (implementation ).

    • Generic Algorithm Based on iterator

The core of algorithm abstraction is generic programming. One aspect is to implement the data access interface with a simple generalized concept. An iterator is a pointer abstraction. Iterator, which can point to data and traverse the data in the container.

 
// Container's support for iterator, [Begin, end)Range access, remember that the range is in the form of forward closed and backward open, that is, there is a passs the end flag, convenient traversal. While (! = Pass_the_end_pos)
 
Template <class T> class list {void push_back (const T & T); // append t to list. typedef... iterator; iterator begin (); iterator end ();};
 
// The algorithm is not written for a specific container.
 
Template <class inputiterator, class T> bool contains (inputiterator first, inputiterator beyond, const T & Value) {While (first! = Beyond) & (* first! = Value) ++ first; Return (first! = Beyond );}

We can use the above algorithm for arrays. iterator is a pointer.

Int A [100]; //... initialize elements of A. bool found = contains (a, A + 100, 42 );
 
It can be used in part of an array.
 
Bool in_first_half = contains (a, A + 50, 42); bool in_third_quarter = contains (a + 50, A + 75, 42); it can also be used on the list container
 
List <int> ls; //... insert some elements into ls. bool found = contains (LS. Begin (), ls. End (), 42 );
 
// Generalized copy Algorithm
 
Template <class inputiterator, class outputiterator> outputiterator copy (inputiterator first, inputiterator beyond, outputiterator result) {While (first! = Beyond) * result ++ = * First ++; return result ;}
 
// Copy from array A1 to array A2
 
Int A1 [100]; int A2 [100]; //... initialize elements of a1.copy (A1, A1 + 100, A2 );
 
NOTE: If LS is empty while copying from A to list ls, it cannot be achieved because ls. beign () = ls. End ()
STL provides a way to solve such problems between concepts.Adapter. The adapter hereBack_inserterYesA model of the output iteraoter Concept.
 
That is to say, it supports data writing. It uses the list container as the input. when data is written to iteraor, its value assignment function = is called, and =Implemented to add this element to the tail of the container it owns.
 
List <int> ls;Copy (A1, A1 + 100, back_inserter (LS ));
 
The implementation of back_inserter will be provided later.

Similarly, the STL contains an adapter between C ++ IO/stream and iterator,

 
Copy (istream_iterator <int> (CIN), istream_iterator <int> (), ostream_iterator <int> (cout, "\ n "));

All Integer numbers obtained from the standard input are output to the standard output, and \ n is output between numbers. Istream_iterator <int> () indicates the end of the interval.Past-the-end positionFor this range

    • A partially implemented iterator
Template <class T> class const_value {T; public: // default constructible! Const_value () {} explicit const_value (const T & S): T (s) {} // assignable by default. // equality comparable (not so easy what that shoshould mean here) bool operator = (const const_value <t> & cv) const {return (this = & cv );} bool Operator! = (Const const_value <t> & cv) const {return! (* This = CV);} // trivial iterator: const T & operator * () const {return t;} const T * operator-> () const {return & operator * ();} // input iteratorconst_value <t> & operator ++ () {return * This;} const_value <t> operator ++ (INT) {const_value <t> TMP = * This; ++ * This; return TMP ;}};

 

This iterator points to a constant and the access range is infinitely large.

Int A [100]; const_value <int> CV (42); copy_n (CV, 100, a); // fills a with 100 times 42.

    • Function object

The so-called function object is the object instance of the class that implements the operator () member function through the internal overload of the class.

Compared with function pointers, function objects have great advantages and are more suitable for generic function parameters.

    1. Function objects can hold their own local states. Class Object, which can have its own variables. (An example is provided later in this section)
    2. Function objects have adaptability in component technology, and some modifier conditions can be added to them to change their state. (The following section provides an example)

Function object usage:

 
Template <class T> struct equals {bool operator () (const T & A, const T & B) {return a = B ;}};
 
Template <class inputiterator, class T, class EQ> bool contains (inputiterator first, inputiterator beyond, const T & Value, EQ eq) {While (first! = Beyond )&&(! Eq (* First, value) + + first; Return (first! = Beyond );}
 
Int A [100]; //... initialize elements of A. bool found = contains (a, A + 100, 42, equals <int> ());

Now, because the function object can hold a local state, we can implement an equals with an error range:

I think this example makes sense. Otherwise, you do not need to add the equals function as a parameter of the contains function.

Template <class T> struct eps_equals {T Epsilon; eps_equals (const T & EPS): Epsilon (EPS) {} bool operator () (const T & A, const T & B) {return (a-B <= epsilon) & (B-A <= epsilon) ;}; bool found = contains (a, A + 100, 42, eps_equals <int> (1 ));

This shows that the search is successful. If a contains or 43.

 
Template <class T> struct count_equals {size_t & count; count_equals (size_t & C): Count (c) {} bool operator () (const T & A, const T & B) {+ + count; return a = B ;}}; size_t counter = 0; bool found = contains (a, A + 100, 42, count_equals <int> (Counter); // counter contains number of comparisons needed.

In this example, the Conter records the number of queries.

    • Iterator traits

 

See EFC ++ Clause 47.
Struct iterator_over_ints {typedef int value_type ;//...};
Template <class iterator> struct iterator_traits {typedef typename iterator: value_type ;//...};

Iterator_traits <iterator_over_ints>: value_type

 
// Bitwise pointer
 
Template <Class t> Struct iterator_traits<T *>{Typedef t value_type ;//...};
 
Iterator_traits <int *>: value_type
 
Iterator traits also defines difference_type, iterator_category, pointer type, and
 
Refernce type.
 
Using traits technology, we can determine the data type and take appropriate measures.

Template <class inputiterator, class T, class EQ>
Bool contains (inputiterator first, inputiterator beyond, const T & value,
EQ eq = equals <typename iterator_traits <inputiterator >:: value_type> ()){
While (first! = Beyond )&&(! Eq (* First, value )))
++ First;
Return (first! = Beyond );
}

// Alternative solution to default using overloaded dispatch function
//
// Template <class inputiterator, class T>
// Bool contains (inputiterator first, inputiterator beyond, const T & Value ){
// Typedef typename iterator_traits <inputiterator >:: value_type;
// Typedef equals <value_type> equal;
// Return contains (first, beyond, value, equal ());
//}

Now the contains with EQ function parameters can be used as well. In fact, it is because a is an int *. For the traits that is specific to the pointer, the Data Type int pointed to by a is obtained. Similar to other iterator.

Assert (contains (a, A + 6, 42 ));
Assert (contains (a, A + 3, 42 ));

STL has applied many traits techniques in many other places, Char_traitsTo define the capacity test and other operations forCharacter Type.

In addition, this character traits class is used asTemplate parameterFor Basic_stringClass Template, Which allows

 Adaption of the string class to different character sets.

      • Function objects that can be configured.

     

     

     

    For more information about this section, see article 40th of objective STL. If a class is a function Child, it should be compatible.

    The allocatable feature is unique to function objects, and function pointers cannot. A function pointer can be a valid model for a function object, but it cannot be a valid model of an adaptable function object.

    The function pointer can also be competent where a function object is required. However, if a function object can be attached, the function pointer is powerless.

    The previous function object equalsFrom above cocould be derived fromSTD: binary_functionTo declare the appropriate types.

     
    # Include <functional> template <class T> struct equals:Public STD: binary_function <t, t, bool>{Bool operator () (const T & A, const T & B) {return a = B ;}

     

     
    Template <class arg1, class arg2, class result> struct binary_function {typedef arg1 first_argument_type; typedef arg2 second_argument_type; typedef result result_type ;};

     

    From the definition of binary_function in STL, we can regard it as equals inheriting binary_function, which is actually to tell it and future applications what are the two input parameter types and the output parameter types.

    First look at an application instance, the use of not1

    Example

     
    // Not1 example # include <iostream> # include <functional> # include <algorithm> using namespace STD; struct isodd: unary_function <int, bool> {bool operator () (const Int & X) const {return X % 2 = 1 ;}}; int main () {int values [] = {1, 2, 3, 4, 5}; int CX; cx = count_if (values, values + 5, not1 (isodd (); cout <"there are" <CX <"elements with even values. \ n "; return 0 ;}

    Output:

     
    There are 2 elements with even values.

     
    Calculate the number of even numbers in values [], but we only have the isodd function. If we don't want to write iseven any more, we can use the not1 adapter to conveniently and directly use it.
    Not1 (isodd) replaces iseven function. However, note that the defined isodd must inherit the unary_function <int, bool> to have the matching feature. Otherwise
     
    Not1 cannot be used. Here, unary_function refers to the combination of an input parameter and an output parameter function object. Why inherit it.
     
    Let's take a look at the implementation of not1:
    Template <class predicate> inline unary_negate <predicate> not1 (const predicate & Pred) {return unary_negate <predicate> (Pred );}
    We can see that not1 only provides one interface, which is a helper function of unary_negate. It internally calls unary_negate <predicate> (Pred) implementation.
     
    Template <class predicate> class unary_negate: Public unary_function <typename predicate: argument_type, bool> {protected: predicate Pred; public: explicit evaluate (const predicate & X): PRED (X) {} bool operator () (constTypename predicate: argument_type & X) Const {return! PRED (x );}};
    Call it here! PRD (x) We need to know the type of X, operator () (const typename predicate: argument_type & X), which is provided by predicate, specifically, this is provided by the real parameter isodd.
     
    If not1 (isodd () is used, the predicate is converted to the type isodd. Therefore, we need the isodd class to provide its operator ()-operated data type. This is why we need to inherit unary_function <int, bool>.
     
    Complicated internal implementation brings convenience and great flexibility to user applications. If the function object to be operated has two input parameters, use not2, and the function object must inherit binary_function.
     
    The specific application uses iterator to traverse the interval. Assume that iterator ITER calls
     
    Not1 (isodd () (* ITER)-> unary_negate (isodd () (* ITER)->! Isodd () (* ITER) // isodd () is a temporary function object.
     
    Let's look at the bind2nd application instance.
     
    Int main (INT argc, char ** argv) {If (argc! = 2) Throw ("Usage: Random integer \ n"); remove_copy_if (istream_iterator <int> (CIN), istream_iterator <int> (), ostream_iterator <int> (cout, "\ n"), not1 (bind2nd (modulus <int> (), atoi (argv [1]); Return 0 ;}

    In this example, All integers selected from the standard input are copied to the output. The condition here is that it cannot be divisible by the program input parameter atoi (argv [1.

    Bind2nd is also a hepler function, which actually generates a binder2nd function object. It accepts a bianry function object, a fixed value, and returns an unary function object. The two input parameters are changed to one input parameter. The original

    The second parameter is bound with a fixed value.

    About bind2nd

    Return function object with second parameter binded

    This functionConstructs an unary function object from the binary function objectOPByBinding its second parameter to the fixed valueX.

     
    Template <class operation, class TP> inline binder2nd <operation> bind2nd (const operation & FN, const TP & X) {typedef typename operation: second_argument_type arg2_type; return binder2nd <operation> (FN, arg2_type (x ));}
    Template <class operation> class binder2nd: Public unary_function <typename operation: Large, typename operation: result_type> {protected: Operation op; typename operation: second_argument_type value; public: binder2nd (const operation & X, const typename operation: second_argument_type & Y): op (x), value (y) {} typename operation: result_typeoperator () (const typename operation:: first_argument_type & X) const {return OP (x, value );}};
     
    Therefore, not1 (bind2nd (modulus <int> (), num) (* ITER)->! Bind2nd (modulus <int> (), num) (* ITER)
     
    ->! Binder2nd (modulus <int> (), num) (* ITER)->! Modulus <int> (* ITER, num)
      • Implementation of the back_inserter Adapter

    Template <class inputiterator, class outputiterator>

    Outputiterator copy (inputiterator first, inputiterator beyond, outputiterator result ){

    While (first! = Beyond)* Result ++= * First ++;

    Return result;

    }

     
    List <int> ls; copy (A1, a_1 + 100,Back_inserter (LS ));
     
     
     
    // Back_inserter is a helper function that calls back_insert_iterator internally.
     
    Template <class container> inline back_insert_iterator <container> back_inserter (container & X) {return back_insert_iterator <container> (x );}

     

    Template <class container> class back_insert_iterator {protected:Container * container;Public: typedef container container_type; typedef output_iterator_tag iterator_category; typedef void value_type; typedef void difference_type; typedef void pointer; typedef void reference; explicit resolve (container & X): Container(& X){} Back_insert_iterator <container> &Operator = (Const typename container: value_type & Value ){Container-> push_back (value); // key magic is here! Return * this;}Back_insert_iterator <container> & operator * () {return * This ;}Back_insert_iterator <container> & operator ++ () {return * This ;}Back_insert_iterator <container> & operator ++ (INT) {return * This ;}};
      • Function allocation (overloading) based on the iterator type during compilation)

     
    Struct detail {}; struct output_iterator_tag {}; struct forward_iterator_tag: Public region {}; struct detail: Public forward_iterator_tag {}; struct detail: Public region {};

    An iterator is assumed to have a local typeIterator_categoryThat is defined to be one of these tags.

     
    Struct some_iterator {typedef forward_iterator_tag iterator_category ;//...};

    This iterator category is accessed using iterator traits. Now we can implement a genericDistanceFunction (original implementation as it is in the STL ):

    Template <class inputiterator> inline typename identifier <inputiterator>: vertex _ distance (inputiterator first, inputiterator last, identifier) {typename identifier <inputiterator>: difference_type n = 0; while (first! = Last) ++ first; ++ N; return N;} template <Class Identifier> inline typename iterator_traits <randomaccessiterator >:: difference_type _ distance (randomaccessiterator first, last, response) {return last-first;} template <class inputiterator> inline typename identifier <inputiterator>: vertex (inputiterator first, inputiterator last) {typedef typename identifier <inputiterator> :: iterator_categorycategory; return _ distance (first, last, category ());}

    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.