C++模板知识

目录

引言

一、非类型模板参数

二、类模板的特化

(一)概念

(二)函数模板特化

(三)类模板特化

[1. 全特化](#1. 全特化)

[2. 偏特化](#2. 偏特化)

(四)类模板特化应用示例

三、模板的分离编译

(一)什么是分离编译

(二)模板的分离编译

(三)解决方法

四、模板总结

(一)优点

(二)缺陷


引言

在C++编程世界里,模板是一项极为强大且灵活的特性,它能让我们编写出通用、可复用的代码。今天,就让我们深入探究C++模板的几个关键方面:非类型模板参数、类模板的特化以及模板的分离编译。

一、非类型模板参数

模板参数分为类型形参与非类型形参。类型形参,我们常见于模板参数列表中,通常紧跟在 class 或者 typename 之后。而非类型模板参数则别具一格,它使用一个常量作为类(函数)模板的一个参数,在模板中可当作常量使用。

代码示例

cpp 复制代码
cpp   
namespace bite {
    // 定义一个模板类型的静态数组
    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 _size; }
        bool empty() const { return 0 == _size; }

    private:
        T _array[N];
        size_t _size;
    };
}

注意事项

1.浮点数、类对象以及字符串是不允许作为非类型模板参数的,用整形初始化

  1. 非类型的模板参数必须在编译期就能确认结果。

二、类模板的特化

(一)概念

模板能实现与类型无关的代码,但遇到特殊类型时,可能会产生错误结果,这就需要对模板进行特化。特化即在原模板类基础上,针对特殊类型进行特殊化的实现方式。模板特化主要分为函数模板特化与类模板特化。

(二)函数模板特化

特化步骤

  1. 首先得有一个基础的函数模板。

2**. 使用关键字 template 后跟一对空的尖括号 <> 。**

  1. 函数名后紧跟一对尖括号,在尖括号中指定需要特化的类型。

  2. 函数形参表必须要和模板函数的基础参数类型完全相同,否则不同编译器可能会报奇怪错误。

代码示例

cpp 复制代码
cpp   
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right) {
    return left < right;
}

// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right) {
    return *left < *right;
}

int main() {
    cout << Less(1, 2) << endl;

    Date d1(2022, 7, 7);
    Date d2(2022, 7, 8);
    cout << Less(d1, d2) << endl;

    Date* p1 = &d1;
    Date* p2 = &d2;
    cout << Less(p1, p2) << endl;  // 调用特化之后的版本,而不走模板生成了
    return 0;
}
 

一般情况下,如果函数模板遇到不能处理或者处理有误的类型,为实现简单,通常直接给出特化版本。函数模板不建议过度特化,因为直接编写普通函数实现简单明了,代码可读性高。

(三)类模板特化

1. 全特化

全特化即将模板参数列表中所有的参数都确定化。

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

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

2. 偏特化

偏特化是针对模板参数进一步进行条件限制设计的特化版本,主要有以下两种表现方式:

  • 部分特化:将模板参数类表中的一部分参数特化。
cpp 复制代码
cpp   
// 将第二个参数特化为int
template <class T1>
class Data<T1, int> {
public:
    Data() { cout << "Data<T1, int>" << endl; }
private:
    T1 _d1;
    int _d2;
};
  • 参数更进一步的限制:不仅特化部分参数,还针对模板参数做更进一步的条件限制。
cpp 复制代码
cpp   
// 两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data<T1*, T2*> {
public:
    Data() { cout << "Data<T1*, T2*>" << endl; }
private:
    T1* _d1;
    T2* _d2;
};

// 两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data<T1&, T2&> {
public:
    Data(const T1& d1, const T2& d2) : _d1(d1), _d2(d2) {
        cout << "Data<T1&, T2&>" << endl;
    }
private:
    const T1& _d1;
    const T2& _d2;
};
 

(四)类模板特化应用示例

以按照小于比较的类模板 Less 为例:

cpp 复制代码
cpp   
#include<vector>
#include <algorithm>

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

int main() {
    Date d1(2022, 7, 7);
    Date d2(2022, 7, 6);
    Date d3(2022, 7, 8);

    vector<Date> v1;
    v1.push_back(d1);
    v1.push_back(d2);
    v1.push_back(d3);
    // 可以直接排序,结果是日期升序
    sort(v1.begin(), v1.end(), Less<Date>());

    vector<Date*> v2;
    v2.push_back(&d1);
    v2.push_back(&d2);
    v2.push_back(&d3);
    // 此处直接排序结果错误,因为sort最终按照Less模板中方式比较,只会比较指针,而非指针指向内容
    // 此时需要特化Less类模板来处理
    sort(v2.begin(), v2.end(), Less<Date*>());

    return 0;
}

// 对Less类模板按照指针方式特化
template<>
struct Less<Date*> {
    bool operator()(Date* x, Date* y) const {
        return *x < *y;
    }
};

三、模板的分离编译

(一)什么是分离编译

一个程序(项目)由若干个源文件共同实现,每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件,这种模式就是分离编译模式。

(二)模板的分离编译

当模板的声明与定义分离开,在头文件中声明,源文件中定义时,会出现问题。例如:

a.h

cpp 复制代码
cpp   
template<class T>
T Add(const T& left, const T& right);
 
 
a.cpp
 
cpp   
template<class T>
T Add(const T& left, const T& right) {
    return left + right;
}
 
 
main.cpp
 
cpp   
#include "a.h"
int main() {
    Add(1, 2);
    Add(1.0, 2.0);
    return 0;
}
 

在这种情况下, a.cpp 中编译器没看到对 Add 模板函数的实例化,不会生成具体的加法函数; main.cpp 编译后在链接时找不到 Add<int> 与 Add<double> 的具体代码,就会报错。

(三)解决方法

1. 将声明和定义放到一个文件"xxx.hpp"里或者 xxx.h 也可以,推荐这种方式。
2. 模板定义的位置显式实例化,但这种方法不实用,不推荐。

cpp 复制代码
cpp   
template<class T>
T Add(const T& left, const T& right);
 
 
a.cpp
 
cpp   
template<class T>
T Add(const T& left, const T& right) 
{
    return left + right;
}
 
// template
//int Add(const int& left, const int& right) 
// template
//double Add(const double& left, const double& right) 

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

四、模板总结

(一)优点

  1. 模板复用了代码,节省资源,能实现更快的迭代开发,C++的标准模板库(STL)就是基于模板产生的。

  2. 增强了代码的灵活性,能适应多种数据类型。

(二)缺陷

  1. 模板会导致代码膨胀问题,编译时会针对不同类型实例化出多份代码,也会使编译时间变长。

  2. 出现模板编译错误时,错误信息非常凌乱,难以定位错误根源。

希望通过这篇博客,大家能对C++模板有更深入、全面的理解,在今后的编程中能更好地运用模板特性编写出高质量、可复用的代码。

相关推荐
Cuit小唐1 小时前
C++ 单例模式详解
开发语言·c++·单例模式
爱吃巧克力的程序媛2 小时前
c++ 二级指针 vs 指针引用
c++
mahuifa2 小时前
(36)VTK C++开发示例 ---纹理贴图四边形
c++·vtk·cmake·贴图·3d开发
海码0072 小时前
【Hot 100】94. 二叉树的中序遍历
数据结构·c++·算法·二叉树·hot100
CodeWithMe2 小时前
【C/C++】Linux的futex锁
linux·c语言·c++
Wabi_sabi_x3 小时前
C++设计模式:面向对象的八大设计原则之二
开发语言·c++·设计模式
一道秘制的小菜3 小时前
AimRT从入门到精通 - 04RPC客户端和服务器
linux·运维·服务器·c++·aimrt
虾球xz3 小时前
游戏引擎学习第256天:XBox 控制器卡顿和修复 GL Blit 伽玛问题
c++·学习·游戏引擎·xbox
꧁坚持很酷꧂4 小时前
C++的基础知识(引用、类、封装、继承、多态、函数重载)
开发语言·c++
mahuifa4 小时前
(37)VTK C++开发示例 ---纹理地球
c++·vscode·vtk·3d地球