云计算百科
云计算领域专业知识百科平台

【初阶C++】模板进阶

 

目录

 ​编辑

本节学习目标

1. 非类型模板参数

⚠️非类型模板参数使用限制

2. 模板特化

2.1 函数模板特化

2.2 类模板特化

2.2.1 全特化

2.2.2 偏特化

3. 模板的分离编译

3.1 什么是分离编译

3.2 模板分离编译报错问题

✅两种解决方案

4. 模板优缺点总结

✅模板优点

❌模板缺点

📔考点总结


本节学习目标

  • 掌握非类型模板参数,使用编译期常量作为模板参数,清楚使用限制
  • 理解模板特化:函数模板特化、类模板全特化、类模板偏特化,解决特殊类型逻辑
  • 理解模板分离编译问题,明白链接报错根源,掌握两种解决方案
  • 总结模板的优点与缺陷
  • 1. 非类型模板参数

    模板参数分为两类:

  • 类型形参:template<class T>,T代表一种数据类型。
  • 非类型形参:编译期常量,作为模板内的常量使用。
  • 示例:模拟静态数组,N是非类型模板参数,代表数组大小

    namespace bite
    {
    // N为非类型模板参数,带默认值10
    template<class T, size_t N = 10>
    class array
    {
    public:
    T& operator[](size_t index){return _array[index];}
    const T& operator[](size_t index)const{return _array[index];}
    size_t size()const{return N;}
    bool empty()const{return 0 == N;}
    private:
    T _array[N]; //编译期确定大小的静态数组
    };
    }

    ⚠️非类型模板参数使用限制

  • 只能是编译期可以确定结果的常量,不能传变量。
  • 禁止浮点数、类对象、字符串字面量作为非类型模板参数。
  • 允许:size_t、int、指针、引用等编译期可求值的整型 / 地址常量。

    2. 模板特化

    基础模板实现通用逻辑,但是部分特殊类型(如指针)按照通用模板会得到错误结果。 模板特化:针对某一类特殊类型,单独定制一套实现。 分为:函数模板特化、类模板特化(全特化、偏特化)。

    2.1 函数模板特化

    示例:通用比较Less模板。 普通类型可以正常比较;传入指针时,通用模板比较指针地址,不是指针指向对象内容,逻辑错误。

    template<class T>
    bool Less(T left, T right)
    {
    return left < right;
    }

    //假设Date类已经定义
    int main()
    {
    Less(1,2); //正常,比较数值
    Date d1(2022,7,7);
    Date d2(2022,7,8);
    Less(d1,d2); //正常,比较对象
    Date* p1 = &d1;
    Date* p2 = &d2;
    Less(p1,p2); //错误!比较的是指针地址,不是*p1、*p2对象内容
    return 0;
    }

    ✅函数模板特化步骤:

  • 必须先存在基础函数模板;
  • template<>尖括号为空;
  • 函数名后<特化的类型>;
  • 形参类型必须和基础模板完全匹配。
  • //基础模板
    template<class T>
    bool Less(T left, T right)
    {
    return left < right;
    }

    //针对Date*指针特化
    template<>
    bool Less<Date*>(Date* left, Date* right)
    {
    return *left < *right; //解引用,比较对象内容
    }

    实操经验: 函数模板特化可读性一般,遇到特殊类型,优先直接写普通重载函数,比特化简单好维护。

    //直接重载,替代特化,推荐写法
    bool Less(Date* left, Date* right)
    {
    return *left < *right;
    }

    2.2 类模板特化

    2.2.1 全特化

    全特化:把模板所有参数全部指定为确定类型。

    template<class T1, class T2>
    class Data
    {
    public:
    Data(){cout<<"Data<T1, T2>" <<endl;}
    private:
    T1 _d1;
    T2 _d2;
    };

    //全特化:T1=int,T2=char,全部确定
    template<>
    class Data<int, char>
    {
    public:
    Data(){cout<<"Data<int, char>" <<endl;}
    private:
    int _d1;
    char _d2;
    };

    void Test()
    {
    Data<int,int> d1; //走通用模板
    Data<int,char> d2; //走全特化版本
    }

    2.2.2 偏特化

    偏特化不是只特化一部分参数,包含两种形式:

  • 部分参数特化:只确定一部分模板参数,剩下保留模板参数;
  • 参数进一步限制:对模板参数施加类型约束(例如限定为指针、引用)。
  • template<class T1, class T2>
    class Data
    {
    public:
    Data(){cout<<"Data<T1, T2>"<<endl;}
    private:
    T1 _d1;
    T2 _d2;
    };

    //①部分特化:第二个参数固定为int,T1仍然是模板参数
    template <class T1>
    class Data<T1, int>
    {
    public:
    Data(){cout<<"Data<T1, int>"<<endl;}
    private:
    T1 _d1;
    int _d2;
    };

    //②参数条件限制:两个参数限定为指针类型
    template <typename T1, typename T2>
    class Data <T1*, T2*>
    {
    public:
    Data(){cout<<"Data<T1*, T2*>"<<endl;}
    private:
    T1 _d1;
    T2 _d2;
    };

    类模板特化实战场景:sort排序,容器存指针,需要比较指针指向对象而不是指针地址。

    template<class T>
    struct Less
    {
    bool operator()(const T& x, const T& y) const
    {
    return x < y;
    }
    };

    //对指针类型特化
    template<>
    struct Less<Date*>
    {
    bool operator()(Date* x, Date* y) const
    {
    return *x < *y;
    }
    };

    3. 模板的分离编译

    3.1 什么是分离编译

    C/C++ 工程:多个.h头文件、.cpp源文件;每个 cpp单独编译生成 obj 目标文件,最后链接器把所有 obj 合并成可执行程序。

    3.2 模板分离编译报错问题

    错误写法:模板声明放.h,模板实现定义放在.cpp。

    // a.h
    template<class T>
    T Add(const T& left, const T& right);

    // a.cpp
    template<class T>
    T Add(const T& left, const T& right)
    {
    return left + right;
    }

    // main.cpp
    #include "a.h"
    int main()
    {
    Add(1,2);
    Add(1.0,2.0);
    return 0;
    }

    编译过程分析:

  • a.cpp单独编译,本文件看不到Add<int>、Add<double>实例调用,不会生成实例化的机器代码。
  • main.cpp看到模板声明,编译通过;但是链接的时候找不到Add<int>、Add<double>的 obj 实现,报无法解析的外部符号链接错误。
  • 模板是编译期实例化:只有看到完整模板定义,编译器才会生成对应类型实例代码。

    ✅两种解决方案

  • 【工程推荐】声明和实现全部放在同一个头文件,后缀一般写.hpp。 .hpp头文件把模板声明 + 实现写在一起,#include时编译器看到完整模板定义,可以按需实例化。
  • 显式实例化(极少用,不推荐),提前手动实例化需要的类型,扩展性差。
  • 【分离编译扩展阅读】
    为什么C++编译器不能支持对模板的分离式编译-CSDN博客

    4. 模板优缺点总结

    ✅模板优点

  • 代码复用,一套逻辑适配多种类型,STL 标准库大量基于模板实现。
  • 代码灵活性高,编译期实例化,运行没有额外开销。
  • ❌模板缺点

  • 代码膨胀:不同类型都会实例生成一份代码,可执行文件体积变大;编译时间增加。
  • 编译报错信息冗长晦涩,报错定位困难。
  • 📔考点总结

  • 非类型模板参数:编译期常量;不能用浮点数、对象、字符串,只能编译期确定的值。
  • 模板特化:针对特殊类型定制实现。
    • 函数模板特化:优先考虑普通重载替代特化;
    • 类模板:全特化(所有参数确定);偏特化(部分参数固定 / 参数施加约束,如指针)。
  • 分离编译大坑:模板不要把头文件声明和 cpp 实现分开放;统一放到.hpp;否则链接报无法解析外部符号。
  • 模板优点:复用、灵活;缺点:代码膨胀、报错信息难懂。
  • 面试简答 Q:非类型模板参数可以传 double 吗?

    A:不可以,只接受编译期整型常量,浮点数、对象、字符串不允许。

    Q:模板为什么不能头声明、cpp 放实现?

    A:cpp 编译单元看不到调用的类型,不会实例化生成函数代码,链接找不到符号报错;建议全部写在 hpp 头文件。

    Q:类模板偏特化和全特化区别?

    A:全特化所有模板参数全部指定;偏特化可以只固定部分参数,或者对参数施加条件(指针、引用)。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【初阶C++】模板进阶
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!