Skip to main content
  1. cpp/

4. 模板 template

4 mins
Table of Contents

模板 template
#

资料1 资料2

  • 模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。

  • 形式:

    • 函数模板:仅参数类型不同的函数
      template <class 形参名,class 形参名,......> 
      返回类型 函数名(参数列表)
      {
          函数体
      }
    例如:template <class T> void swap(T& a, T& b){};
    
    • 类模板:仅数据成员成员函数类型不同的类
      template <class 形参名,class 形参名,......> 
      class 类名
      { ... }
    例如:template<class T> class A{
            public: T a; T b; 
            T hy(T c, T &d);};
    
    • 在类模板外部定义成员函数
    template<模板形参列表> 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体};
    例如:template<class T1, class T2> void A<T1,T2>::h(){};
    
  • 成员模板、模板特化、模板模板参数资料

模板特化
#

  • 模板的局部具体化,特化

  • 使用模板的时候,编译器会优先使用模板特化,且特化版本可以有任意个

    //泛化:
    template <class Key>
    struct hash { };
    //特化:
    template<>
    struct hash<char> {
      size_t operator() (char x) const { return x; }
    };
    template<>
    struct hash<int> {
      size_t operator() (int x) const { return x; }
    };
    template<>
    struct hash<long> {
      size_t operator() (long x) const { return x; }
    };
    

模板偏特化
#

  • 个数上的偏特化:多个模板参数中只绑定其中的个别,且绑定必须从左到右,不能跳着绑定

    template<typename T, typename Alloc=...>
    class vector {...};
    
    template<typename Alloc=...> // T绑定了bool
    class vector<bool, Alloc> {...};
    
  • 范围上的偏特化:将模板参数的类型范围缩小

    template<typename T> // 不是指针使用该代码
    class C {...};
    
    template<typename U> // 是指针使用该代码
    class C<U*> {...}; // 任意类型缩小为指针
    
    C<string> obj1;  // 不是指针
    C<string*> obj2; // 是指针
    

模板模板参数
#

  • 模板的参数也是一个模板

    template<typename T,
             template <typenmae T>
                class Container >
    class XCls
    {
    private:
      Container<T> c;
    public:
      ...
    };
    
    template<typename T>
    using Lst = list<T, allocator<T>>;
    
    XCls<string, list> mylist1;  //写法错误
    XCls<string, Lst> mylist2;
    
    template<typename T,
             template <typenmae T>
                class SmartPtr     >
    class XCls
    {
    private:
      SmartPtr<T> sp;
    public:
      XCls() : sp(new T) { }
    };
    XCls<string, shared_ptr> p1;
    Cls<double, unique_ptr> p2;  //写法错误
    Cls<int, weak_ptr> p3;  //写法错误
    XCls<long, auto_ptr> p4;
    
  • 如下不是模板模板参数

    template < class T, class Sequence = deque<T> >
    class stack {
      friend bool operateo== <> (cosnt stack&, const stack&);
      friend bool operateo< <> (cosnt stack&, const stack&);
    protected:
      Sequence c;
      ......
    };
    // 第二个参数list已经绑定了int类型的模板参数,故不是模板模板参数
    // s2 是 s1 的详细写法
    stack<int> s1;
    stack<int, list<int>> s2;
    

模板类、成员模板、虚函数
#

  • 模板类中可以使用虚函数
  • 一个类(无论是普通类还是类模板)的成员模板(本身是模板的成员函数)不能是虚函数

成员模板 (member template)
#

  • class 的成员函数可以是template

  • 成员模板不能是virtual

  • 该语言特性常被用来支持class template内的成员之间的自动类型转换

    template <class T1, class T2>
    struct pair {
    ...
      T1 first;
      T2 second;
      pair():first(T1()), second(T2()) {}
      pair(const T1& a, const T2& b):
        first(a), second(b) {}
      template<class U1, class U2>
      pair(const pair<U1, U2>& p):
        // 必须能够使用以下进行初值设置
        first(p.fisrt), second(p.second) {}
    };
    //使用:
    class Base1 { };
    class Derived1:public Base1 { };
    
    class Base2 { };
    calss Derived2:public Base2 { };
    
    pair<Derived1, Derived2> p;
    pair<Base1, Base2> p2(p);  // p中的Derived1和Derived2自动转换为Base1和Base2,反之则不能
    pair<Base1, Base2> p3(pair<Derived1, Derived2>());
    

C++模板编程
#

参考资料

  • 编译原理:代码编写阶段类型先不确定;在编译器编译时根据函数入参类型再确定,从而生成具体的函数版本。

      1. 编译器并不是把函数模板处理成能够处理任意类的函数;编译器从函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译
      1. 这是因为函数模板要被实例化后才能成为真正的函数,在使用函数模板的源文件中包含函数模板的头文件,如果该头文件中只有声明,没有定义,那编译器无法实例化该模板,最终导致链接错误
  • 为什么成员函数模板不能是虚函数(virtual)?

    因为c++ compiler在parse一个类的时候就要确定vtable的大小,如果允许一个虚函数是模板函数,那么compiler就需要在parse这个类之前扫描所有的代码,找出这个模板成员函数的调用(实例化),然后才能确定vtable的大小,而显然这是不可行的,除非改变当前compiler的工作机制。

C++ 模板类的声明和定义都要放在头文件
#

参考资料

模板定义很特殊。由 template<…> 处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。

  • 具体问题:两个cpp文件,一个cpp中定义的模板另一个可以用吗?不能
    • 原因:在分离式编译的环境下,编译器编译某一个 .cpp 文件时并不知道另一个 .cpp 文件的存在,也不会去查找(当遇到未决符号时它会寄希望于连接器)。这种模式在模板下是不行的,因为模板仅在需要的时候才会实例化出来,所以当编译器只看到模板的声明时,它不能实例化该模板,只能创建一个具有外部连接的符号并期待连接器能够将符号的地址决议出来。然而当实现该模板的.cpp文件中没有用到模板的实例时,编译器懒得去实例化,所以,整个工程的.obj中就找不到一行模板实例的二进制代码,于是连接器也黔驴技穷了。

元编程
#