Cpp Chapter 14: Reusing code in C++ Part2

14.4 Class Templates

) Templates provide parameterized types, which is capable of passing a type name as a recipe for building a class or a function.
) Changes defining a template class:
1 preface class and method definition with

template <class Type>

Here type serves as a generic type specifier(built-in types, classes, etc.)
2 The method name will be altered from

bool Stack::push(const Item & item)
{
    ...
}

to

bool Stack<Type>::push(const Type & item)
{
    ...
}

) A particular actualization of a such a template is called instantiation.
Here comes the class definition and class methods file:

// stacktp.h -- a stack template
#ifndef STACKTP_H_INCLUDED
#define STACKTP_H_INCLUDED

template <class Type>
class stack
{
private:
    enum {MAX = 10};
    Type items[MAX];
    int top;
public:
    Stack();
    bool isempty();
    bool isfull();
    bool push(const Type & item);
    bool pop(Type & item);
};

template <class Type>
Stack<Type>::Stack()
{
    top = 0;
}

template <class Type>
bool Stack<Type>::isempty()
{
    return top == 0;
}

template <class Type>
bool Stack<Type>::isfull()
{
    return top == MAX;
}

template <class Type>
bool Stack<Type>::push(const Type & item)
{
    if (top < MAX)
    {
        items[top++] = item;
        return true;
    }
    else
        return false;
}

template <class Type>
bool Stack<Type>::pop(Type & item)
{
    if (top > 0)
    {
        item = items[top--];
        return true;
    }
    else
        return false;
}

#endif // STACKTP_H_INCLUDED

) To use template classes, you generate template class in this way:

Stack<int> kernels;
Stack<string> colonels;

Here comes the code:

// stacktem.cpp -- testing the template stack class
#include <iostream>
#include <string>
#include <cctype>
#include "stacktp.h"
using std::cin;
using std::cout;

int main()
{
    Stack<std::string> st;
    char ch;
    std::string po;
    cout << "Please enter A to add a purchase order,\n"
         << "P to process a PO, or Q to quit.\n";
    while (cin >> ch && std::toupper(ch) != 'Q')
    {
        while (cin.get() != '\n')
            continue;
        if (!std::isalpha(ch))
        {
            cout << '\a';
            continue;
        }
        switch(ch)
        {
        case 'A':
        case 'a':
            cout << "Enter a PO number to add: ";
            cin >> po;
            if (st.isfull())
                cout << "stack already full\n";
            else
                st.push(po);
            break;
        case 'P':
        case 'p':
            if (st.isempty())
                cout << "stack already empty\n";
            else
            {
                st.pop(po);
                cout <<"PO #" << po << " popped\n";
                break;
            }
        }
        cout << "Please enter A to add a purchase order,\nP to process a PO, or Q to quit.\n";
    }
    cout << "Bye\n";
    return 0;
}

) To use a stack of pointers to strings, you need to have each pointer point to a separate string to work well.

// stcktp1.h -- modified Stack template
#ifndef STCKTP1_H_INCLUDED
#define STCKTP1_H_INCLUDED

template <class Type>
class Stack
{
private:
    enum {SIZE = 10};
    int stacksize;
    Type * items;
    int top;
public:
    explicit Stack(int ss = SIZE);
    Stack(const Stack & st);
    ~Stack() {delete [] items;}
    bool isempty() {return top == 0;}
    bool isfull() {return top == stacksize;}
    bool push(const Type & item);
    bool pop(Type & item);
    Stack & operator=(const Stack & st)
};

template <class Type>
Stack<Type>::Stack(int ss) : stacksize(ss), top(0)
{
    items = new Type [stacksize];
}

template <class Type>
Stack(Type)::Stack(const Stack & st)
{
    stacksize = st.stacksize;
    top = st.top;
    items = new Type [stacksize];
    for (int i = 0; i < top; i++)
        items[i] = st.items[i];
}

template <class Type>
bool Stack<Type>::push(const Type & item)
{
    if (top < stacksize)
    {
        items[top++] = item;
        return true;
    }
    else
        return false;
}

template <class Type>
bool Stack<Type>::pop(Type & item)
{
    if (top > 0)
    {
        item = items[--top];
        return true;
    }
    else
        return false;
}

template <class Type>
Stack<Type> & Stack<Type>::operator=(const Stack<Type> & st)
{
    if (this = &st)
        return *this;
    delete [] items;
    stacksize = st.stacksize;
    top = st.top;
    items = new Type[stacksize];
    for (int i = 0; i < top; i++)
        items[i] = st.items[i];
    return *this;
}

#endif // STCKTP1_H_INCLUDED

Noteworthy:
The prototype for assignment operator declared the return type of the method as Stack rather than Stack:

Stack & operator=(const Stack & st);

This is an abbreviation used only in class scope.
) Here comes the code that uses the simplified version of stack:

// stkoptr1.cpp -- testing stack of pointers
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "stcktp1.h"
const int Num = 10;
int main()
{
    std::srand(std::time(0));
    std::cout << "Please enter stack size: ";
    int stacksize;
    std::cin >> stacksize;
    Stack<const char *> st(stacksize);

    const char * in[Num] = {"a","b","c","d","e","f","g","h","i","j"};
    const char * out[Num];

    int processed = 0;
    int nextin = 0;
    while (processed < Num)
    {
        if (st.isempty())
            st.push(in[nextin++]);
        else if (st.isfull())
            st.pop(out[processed++]);
        else if (std::rand() % 2 && nextin < Num)
            st.push(in[nextin++]);
        else
            st.pop(out[processed++]);
    }
    for (int i = 0; i < Num; i++)
        std::cout << out[i] << std::endl;

    std::cout << "Bye\n";
    return 0;
}

) Next let's come to an array template example:

// arraytp.h -- Array Template
#ifndef ARRAYTP_H_INCLUDED
#define ARRAYTP_H_INCLUDED

#include <iostream>
#include <cstdlib>

template <class T, int n>
class ArrayTP
{
private:
    T ar[n];
public:
    ArrayTp() {}
    explicit ArrayTP(const T & v);
    virtual T & operator[] (int i);
    virtual T operator[] (int i) const;
};

template <class T, int n>
ArrayTP<T,n>::ArrayTP(const T & v)
{
    for (int i = 0; i < n; i++)
        ar[i] = v;
}

template <class T, int n>
T & ArrayTP<T,n>::operator[](int i)
{
    if (i < 0 || i >= n)
    {
        std::cerr << "Error in array limits: " << i << " is out of range\n";
        std::exit(EXIT_FAILUER);
    }
    return ar[i];
}

template <class T, int n>
T ArrayTP<T,n>::operator[](int i) const
{
    if (i < 0 || i >= n)
    {
        std::cerr << "Error in array limits: " << i << " is out of range\n";
        std::exit(EXIT_FAILURE);
    }
    return ar[i];
}

#endif // ARRAYTP_H_INCLUDED

Noteworthy
1 Note the template heading:

template <class T, int n>

This second parameter is called a non-type, as it acts as a particular type. You can use the template in this way:

ArrayTP<double, 12> eggweights;

This initializes an array of 12 doubles called eggweights.
2 Advantage and drawback
Advantage: fast execution, free from new and delele
Drawback: Each array size generate its own template

) As normal classes, you could also use template classes for inheritance or containment.
You could also use templates recursively:

ArrayTP< ArrayTP<int,5>, 10> twodee;

This is the same effect as

int twodee[10][5];

Here comes an example using the recursive call of template to create a two-dimensional array:

template <class T, int n>
class ArrayTP
{
private:
    T ar[n];
public:
    ArrayTP() {};
    explicit ArrayTP(const T & v);
    virtual T & operator[](int i);
    virtual T operator[](int i) const;
};

) You could also use templates with different types combined:

template <class T1, class T2>

Here comes the code using this trick with a Pair class:

// pairs.cpp -- defining and using a Pair template
#include <iostream>
#include <string>
template <class T1, class T2>
class Pair
{
private:
    T1 a;
    T2 b;
public:
    T1 & first();
    T2 & second();
    T1 first() const { return a;}
    T2 second() const { return b;}
    Pair(const T1 & aval, const T2 & bval) : a(aval), b(bval) {}
    Pair() {}
};

template <class T1, class T2>
T1 & Pair<T1,T2>::first()
{
    return a;
}

template <class T1, class T2>
T2 & Pair<T1,T2>::second()
{
    return b;
}

int main()
{
    using std::cout;
    using std::endl;
    using std::string;
    Pair<string,int> ratings[4] =
    {
        Pair<string,int>("a",1),
        Pair<string,int>("b",2),
        Pair<string,int>("c",3),
        Pair<string,int>("d",4),
    };

    int joints = sizeof(ratings) / sizeof(Pair<string,int>);
    cout << "Rating:\t Eatery\n";
    for (int i = 0; i < joints; i++)
        cout << ratings[i].second() << ":\t " << ratings[i].first() << endl;
    cout << "Oops! Revised rating:\n";
    ratings[3].first() = "g";
    ratings[3].second() = 6;
    cout << ratings[3].second() << ":\t " << ratings[3].first() << endl;
    return 0;
}

) You can provide default values for type parameters:

template <class T1, class T2 = int> class Topo {...}

) Template specializations
1 Implicit instantiation
When an object of the desired type is declared, the compiler generates a specialized class definition, using the recipe provided by the template:

ArrayTP<double, 30> * pt;
pt = new ArrayTP<double, 30>;

The compiler only generates a class for ArrayTP<double, 30> at the second sentence because only the second sentence requires declaration of a specific object of the class ArrayTP<double, 30>.
2 Explicit instantiation
When you explicitly use the keyword template and desired type to declare a class, the compiler generates one with the specific data types according to the template instructions even if there is no object of the class needed currently:

template class ArrayTP<string, 100>;

3 Explicit specialization
In some of the cases, the template's methods might work differently for one specific type, so you might want to single out the desired type's method implementations. A specialized class template definition has the following form:

template <> class Classname<specialized-type-name> { ... };

for example, after you explicit specialized template <> class Thing<char *> { ... }, next time you require a class of the char * type, the compiler would use the specialized version rather than generating one by the template.

) Member templates
A template could be a member of a structure, class or template class:

// tempmemb.cpp -- template members
#include <iostream>
using std::cout;
using std::endl;
template <typename T>
class beta
{
private:
    template <typename V>
    class hold
    {
    private:
        V val;
    public:
        hold(V v = 0) : val(v) {}
        void show() const {cout << val << endl;}
        V  Value() const {return val;}
    };
    hold<T> q;
    hold<int> n;
    public:
        beta(T t, int i) : q(t), n(i) {}
        template<typename U>
        U blab(U u, T t) { return (n.Value() + q.Value()) * u / t;}
        void Show() const { q.show(); n.show();}
};

int main()
{
    beta<double> guy(3.5, 3);
    cout << "T was set to double\n";
    guy.Show();
    cout << "V was set to T, which is double, then V was set to int\n";
    cout << guy.blab(10,2.3) << endl;
    cout << "U was set to int\n";
    cout << guy.blab(10.0, 2.0) << endl;
    cout << "U was set to double\n";
    cout << "Done\n";
    return 0;
}

) Templates could also be parameters for templates:

template <template <typename T> class Thing>

Here template <typename T> class is the type and Thing is the parameter. View an example:

// tempparm.cpp -- templates as parameters
#include <iostream>
#include "stacktp.h"

template <template <typename T> class Thing>
class Crab
{
private:
    Thing<int> s1;
    Thing<double> s2; // Thing is the template parameter
public:
    Crab() {};
    bool push(int a, double x) { return s1.push(a) && s2.push(x);}
    bool pop(int & a, double & x) { return s1.pop(a) && s2.pop(x);}
};

int main()
{
    using std::cout;
    using std::cin;
    using std::endl;
    Crab<Stack> nebula; // send Stack template as parameter to template class Crab
    int ni;
    double nb;
    cout << "Enter int double pairs, such as 4 3.5 (0 0 to end):\n";
    while (cin >> ni >> nb && ni > 0 && nb > 0)
    {
        if (!nebula.push(ni,nb))
            break;
    }

    while (nebula.pop(ni,nb))
        cout << ni << ", " << nb <<endl;
    cout << "Done.\n";

    return 0;
}

) Template classes and friends
1 Non-template friend functions to template classes
This means that the function is friend to all possible instantiations of the tempalate, use this form:

template <class T>
class HasFriend
{
    friend void report(HasFriend<T> &);
    ...
};

In this case report could accept a object of specific type as parameter.
Here comes an example:

// frnd2tmp.cpp -- template class with non-template friends
#include <iostream>
using std::cout;
using std::endl;

template <typename T>
class HasFriend
{
private:
    T item;
    static int ct;
public:
    HasFriend(const T & i) : item(i) {ct++;}
    ~HasFriend() { ct--; }
    friend void counts();
    friend void reports(HasFriend<T> &);
};

template <typename T>
int HasFirned<T>::ct = 0;

void counts()
{
    cout << "int count: " << HasFriend<int>::ct << "; ";
    cout << "double conut: " << HasFriend<double>::ct << "\n";
}

void reports(HasFriend<int> & hf)
{
    cout << "HasFriend<int>: " << hf.item << endl;
}

void reports(HasFriend<double> & hf)
{
    cout << "HasFriend<double>: " << hf.item << endl;
}

int main()
{
    cout << "No objects declared: ";
    counts();
    HasFriend<int> hfi1(10);
    cout << "After hfi1 declared: ";
    counts();
    HasFriend<int> hfi2(20);
    cout << "After hfi2 declared: ";
    counts();
    HasFriend<double> hfdb(10.5);
    cout << "After hfdb declared: ";
    counts();
    reports(hfi1);
    reports(hfi2);
    reports(hfdb);

    return 0;
}

2 Bound template friend functions to template classes
This means that the type of the friend is determined by the type of class when it is instantiated.

3 Unbound template friend functions to template classes
This means that all specializations of the friend are friends to each specialization of the class.

posted @ 2018-11-15 14:25  Gabriel_Ham  阅读(144)  评论(0编辑  收藏  举报