当前位置: 首页 > 工具软件 > bTemplate > 使用案例 >

template详解

韩景辉
2023-12-01

1. 函数模板

1. 函数模板定义

关键字:“template开头”“参数列表非空”

  1. 定义一定要以:template<…>开头(为了解释为什么 inline 在第 2 位置)

  2. 定义中模板参数列表不能为空(模板特例化中参数列表可以为空)。

    template<tempname T> inline T funcName(const T &a, const T &b) { return (a + b); }  //inline
    template<tempname T> T funcName(const T &a, const T &b) { return (a + b); }
    template< > void funcName() {  }    //错误:定义时模板参数不能为空。
    

2. 函数参数

关键字:“非类形形参为常数”“泛型编程规则”“const引用”

在下面的代码中,其中T为类型形参,类型在编译器进行实例化(instantiate)时确定,T还可以用来做类型转换。v为非类型形参,v的实例必须为常数。

template<class T, T v> void foo(T &a, T &b)
{
    b = v;
    cout<<"a = "<<a<<ends<<"b = "<<b<<endl;
    a = b;
}

int main()
{
    int m = 1;
    int n = 2;
    foo<int, m>(m, n);  //error C2971: “foo”: 模板参数“v”:“m”: 局部变量**不能用作非类型参数**
    cout<<"a = "<<m<<ends<<"b = "<<n<<endl;
    return 0;
}

泛型编程中,函数的参数常常为const引用,来保证参数不仅可为拷贝类型,也可以是非拷贝类型,并且能够保证对源数据的只读性。函数内部的判断符号使用 <,目的是减小函数对要处理类型的要求,因为有的类型肯可能并没有对其他运算符进行重载。

3. 实例化

实例化目前我所了解的有两种:

  • 编译器通过传入的实参进行推断类型形参的类型(即模板实参推断:template argument deduction/*推理*/),但是无法为非类形形参进行推断。

  • 在调用函数时进行指定类型形参非类形形参的类型。

    template<class T, T v> void foo(T &a, T &b) { ... }
    foo(m, 5);  //error C2783: “void foo(T &,T &)”: 未能为“v”推导 模板 参数
    
    /*正确的实例化*/
    template<class T, class V> void foo(T &a, V &b) { ... }
    foo(5, 5);  //推断
    
    template<class T, T v> void foo(T &a, T &b) { ... }
    foo<int, 6>(5, 5);  //指定
    

2. 类模板

1. 定义与实例化

大致与函数模板的定义相同。

template<class T> class Bob
{
public:
    void func(T &, T &);
    template<class It> void foo();
private:
    T data;
};

实例化一般在创建对象时指定类型,也可以进行类的特例化。

Bob<int> b; //实例化

2. 类模板的成员函数

关键字:“隐式内联”“部分实例化”“成员模板”

  • 定义在类作用域内的成员函数隐式的被定义为内联函数

  • 成员函数在外部定义时,开头必须以 template<..> 开头。

    template<class T> void Bob<T>::func(T &a, T &b)
    {
        data = a + b;
    }
    
  • 成员模板函数不能是虚函数

在类的外部,只有当遇到类名以后,才算该类的作用域。因此 Bob<T>以后均为 Bob类的作用域,类的作用域中可以将 template

3. 编译模板

关键字:“定义在头文件”“部分实例化”

当编译器遇到一个模板定义的时候,它并不生成代码。只有当我们实例化出模板一个特定的版本但是时候(即特例化时),编译器才会生成代码。或者在我们使用模板时,编译器才生成代码。

  • 模板函数和模板类的成员函数通常在头文件中定义

  • 编译模板的三个过程:编译模板本身,编译器遇到使用模板,模板实例化。

  • 错误一般在第三个阶段,模板实例化中检测到。模板实例化中是部分实例化,并非全部。仅仅实例化用到的模板。

4. 默认模板参数

默认模板参数时,跟函数默认实参一样,对于一个模板参数,只有当它右侧所有参数都有默认实参时,它才可以具备默认实参。

template<class A = int, class B> A foo2() { ... }   //error C4519: 仅允许在类模板上使用默认模板参数

默认值在使用模板的时候均可以修改(c++11新标准)

template<class A = int, int b = 0> int foo3() { return b; }

int result = foo3<int, 7>();    //result = 7, ok
int result = foo3<double, 7>(); //result = 7, ok

5. 特例化模板(template specialization)

当面对特定的类型,模板不合适的时候,我们可以为这种类型量身打造一款特例化版本。

//定义
template <bool b, typename B, typename C> class {
    ...
};
//部分特例化
template <> class<true, int, int> {
    ...
};
//特例化
template <typename B, typename C> class<false, B, C> {
    ...
};

未了解的关于C++模板的知识

学无止境,还有很多未了解的C++模板知识和C++模板的使用(比如traits..),部分列举如下:

  1. 控制实例化:在大系统中,多个文件中实例化相同的模板,开销严重。使用显示实例化(explicit/*明确的*/ instantitation)来避免开销。

    extern template class Bob; //声明:告诉编译器,在程序的其他位置(或者其他文件中)有string的实例化定义
    tempalte int compare (const int&, const int&); //定义

  2. 在运行是绑定删除器,在编译时绑定删除器(与智能指针有关)。

  3. 模板的参数推断。

  4. 理解std:move

  5. 转发:有的时候需要将一个或者多个实参连同类型转发给其他函数。

  6. 可变参数的模板

    • sizeof… 运算符

    • 可变参数模板的编写

    • 包扩展

    • 转发参数包

  7. struct和template连用实现traits

    struct里面定义了类型静态常数,需要用结构体名打点调用:

    structName::defType i = value;
    
    template<class T, T v>
    struct integral_constant {
        static const T value = v;
        typedef T value_type;
        typedef struct integral_constant<T, v> type;
    };
    
    int i = struct integral_constant::value;
    
 类似资料: