浅墨浓香

想要天亮进城,就得天黑赶路。

导航

第5课 模板的细节改进(2)_模板模板参数

Posted on 2017-10-04 10:51  浅墨浓香  阅读(668)  评论(1编辑  收藏  举报

1. 模板模板参数 (Template template parameter)

(1)模板模板参数:即模板的参数也是一个模板

(2)声明格式形如:

template<typename T, template<typename U> class Container> //参数Container本身是一个模板,其参数为U类型。
class XCLS
{
private:
    Container<T> c; 
};

2. 模板模板参数的实参匹配

(1)实参与Container模板的参数必须完全匹配

template<typename T>
using Lst = std::list<T, std::allocator<T>>;

XCLS<string, Lst> mylist; //这里的实参不能传入list,因为list模板有两个参数,与Container模板(只有一个参数)不匹配,可以用using来重定义来解决该问题。

(2)当不需要使用模板参数时,参数名可以省略不写。如

   template<typename T2, template<typename> class CONT2> //注意,第2个typname后面没有参数名

【编程实验】用不同容器实现的栈

 //test1.cpp

#include <iostream>
#include <deque>
#include <vector>

//1. 不使用using的模板模板参数(注意:第2个参数的类型是模板,而不是一般的类型!!!)
//   由于第2个参数CONT是一个容器类的模板,这个模板本身要求两个参数ELEM和ALLOC。所以
//   必须在第2个参数CONT(模板模板参数,也用template声明)中完整的写出,如下:
template<typename T, template<typename ELEM, typename ALLOC = std::allocator<ELEM> > 
                     class CONT = std::deque>
class Stack
{
private:
    CONT<T> elems;  //CONT<T, allocator<T> >
                    //deque<T, allocator<T> >
public:
    void push(const T& x){elems.push_back(x);}
    void pop(){elems.pop_back();}
    
    T& top(){return elems.back();}
    bool empty() const {return elems.empty();}
    

    //缺省赋值运算符要求=两边具有相同的元素类型。通过定义一个模板形式的赋值运算符,
    //元素类型不同的两个栈就可实现相互赋值,如:
    //Stack<int> is1, is2;
    //Stack<double> ds3;
    //...
    //is1 = is2;  // OK: 具有相同类型的栈 
    //is3 = is1;  // ERROR:两边栈的类型不同    
    template<typename T2, template<typename ELEM2, typename ALLOC = std::allocator<ELEM2> > 
                    class CONT2>
    Stack<T, CONT>& operator=(const Stack<T2, CONT2>& rhs)
    {
        if((void*)this == (void*)&rhs)
            return *this;  //避免自赋值
        
        Stack<T2, CONT2> tmp(rhs);
        elems.clear();
        
        while(!tmp.empty())
        {
            elems.push_front(tmp.top());
            tmp.pop();
        }
        
        return *this;
    }
};

//2. 使用using优化模板模板参数(注意:第2个参数的类型是模板,而不是一般的类型!!!)
template<typename T>
using vec = std::vector<T, std::allocator<T>>;

template<typename T>
using deq = std::vector<T, std::allocator<T>>;

template<typename T, template<typename> class CONT>
class Stack2
{
private:
    CONT<T> elems;  //CONT<T, allocator<T> >
                    //deque<T, allocator<T> >
public:
    void push(const T& x){elems.push_back(x);}
    void pop(){elems.pop_back();}
    
    T& top(){return elems.back();}
    bool empty() const {return elems.empty();}
    

    //缺省赋值运算符要求=两边具有相同的元素类型。通过定义一个模板形式的赋值运算符,
    //元素类型不同的两个栈就可实现相互赋值    
    template<typename T2, template<typename> class CONT2>
    Stack2<T, CONT>& operator=(const Stack2<T2, CONT2>& rhs)
    {
        if((void*)this == (void*)&rhs)
            return *this;  //避免自赋值
        
        Stack2<T2, CONT2> tmp(rhs);
        elems.clear();
        
        while(!tmp.empty())
        {
            elems.push_front(tmp.top());
            tmp.pop();
        }
        
        return *this;
    }
};

int main()
{
    Stack2<int, vec> stack1; //注意,第2个参数为模板
    for(int i=0; i<10;i++){
        stack1.push(i);
    }
    
    Stack2<double, vec> stack2;
    stack2 = stack1;
    
    while(!stack2.empty()){
        std::cout << stack2.top() << std::endl;
        stack2.pop();
    }
    return 0;
}

3. 模板模板参数+using的妙用

(1)实验要求:写一个能测试各种容器效率的函数或类,传入的参数为类型!

(2)程序的进化

  ①最初的想法:利用普通函数来实现(天方夜谭)

//最原始的想法:利用普通函数实现
void test_moveable(Container cntr, T elem)
{
    //传入的是对象cntr,却要拿它的类型做文章。(天方夜谭:无法获得Container类型!)
    Container<T> c;
    
    //插入大量的元素
    for(long i=0; i<SIZE; ++i)
    {
        c.insert(c.end(), T()); //同样elem是对象,无法获得T类型
    }
    
    output_static_data(T()); //打印T的静态数据
    //测试大量元素的拷贝构造
    Container<T> c1(c); 
    ...
    
    //测试大量元素的移动拷贝构造
    Container<T> c2(std::move(c));
    ...
    
    c1.swap(c2);
}

//函数调用:接口很漂亮,但无法实现这个目的。因为函数只能传入
//对象或变量,而不能传入类型!
test_moveable(list, MyString); //error,不能传入类型!
test_moveable(list, MyStrNoMove); 

  ②改进的做法:利用函数模板实现(天方夜谭)

//进化的做法:利用函数模板实现
template<typename Container, typename T>
void test_moveable(Container cntr, T elem)
{
    //由于Container是个普通的模板参数,而不是模板模板参数。因此
    //不能像下列方式被使用!
    Container<T> c;  //typename Container<T> c; 也不行!
    
    //插入大量的元素
    for(long i=0; i<SIZE; ++i)
    {
        c.insert(c.end(), T()); 
    }
    
    output_static_data(T());

    Container<T> c1(c); 
    Container<T> c2(std::move(c));

    c1.swap(c2);
}
//函数调用:注意传入的是对象。list()为演示之用,实际上无法这样产生对象。
test_moveable(list(), MyString()); //error,不能传入类型!
test_moveable(list(), MyStrNoMove()); 

  ③不完美方案:利用迭代器与萃取机

//不完美的进化:利用迭代器与萃取机(可以解决问题,但不完美!因为用到了stl中的迭代器,
//这也限制了该函数只能用来测试stl标准库中的容器类)
template<typename Container>
void test_moveable(Container c)
{
    //由于STL库中的迭代器都包含了元素的类型,可以使用traits萃取出来
    typedef typename iterator_traits<typename Container::iterator>::value_type Valtype;
    
    //插入大量的元素
    for(long i=0; i<SIZE; ++i)
    {
        c.insert(c.end(), Valtype()); //利用元素类型产生对象!
    }
    
    output_static_data(*(c.begin());//注意: output_static_data(const T& obj)

    Container<T> c1(c); 
    Container<T> c2(std::move(c));

    c1.swap(c2);
}

//函数调用:不完美,函数的接口变了。我们希望的是接入类型,而不是对象!
test_moveable(list<MyString>()); 
test_moveable(list<MyStrNoMove>());
test_moveable(vector<MyString>()); 
test_moveable(vector<MyStrNoMove>()); 

  ④终极方案:利用模板模板参数与模板别名

//完美方案(利用模板模板参数+模板别名)
//(Container参数是一个模板!注意其声明部分)
template<typename T, template<typename> class Container> 
class XCls
{
    Container<T> c; //注意,Container本身是一个模板,只有一个参数!
                    //而stl中容器类有两个参数(如vector<T, allocator<T>>),
                    //会出现参数不匹配!如何解决:见调用时using的使用!
                    
public:
    XCls(){
        for(long i=0; i<SIZE; ++i)
        {
            c.insert(c.end(), T()); 
        }
    
        output_static_data(T());

        Container<T> c1(c); 
        Container<T> c2(std::move(c));

        c1.swap(c2);        
    }    
}; 

//注意,template不能定义在函数内部!
template <typename T>
using vec = std::vector<T, std::allocator<T>>;

template <typename T>
using lst = std::list<T, std::allocator<T>>;

//调用(理想的接口:传入类型!)
XCls<MyString, vec> c1; //不能写成XCls<MyString, vector> cl,因为Container与
                           //vector的模板的参数不匹配。
XCls<MyString, lst> c2;