乐于分享
好东西不私藏

C++核心:模板

C++核心:模板
参考:https://zh.cppreference.com/cpp/language/templates

一:模板初识

模板是C++中实现泛型编程的核心工具。它允许你编写与类型无关的代码,从而定义一族类型、函数或变量。模板由一个或多个模板形参参数化,这些形参在实例化时会被具体的模板实参替换。

语法如下:

template <class/typename T> 声明

·template:模板声明关键字。

·class/typename:类型形参关键词。

·T:数据类型。也可以写成其它字母,通常为大写。

那为什么会有模板呢?假如我们写一个加法函数,很简单的需求,如下:
#include <iostream>using namespace std;// 定义一个加法函数intadd(int a, int b) {int sum = a + b;return sum;}intmain() {int a = 10;int b = 20;int sum = add(a, b);   // 30	cout << "a + b = :" << sum << endl;    return 0;}
那么,假如需要实现double类型的加法函数呢,是不是需要写一个返回值类型为double,形参为double的函数,假如需要实现float类型的加法函数呢,又需要写一个写一个返回值类型为float,形参为float的函数。这就导致代码冗余且不简洁。
于是,模板就出现了,它通过定义一个“模板”,无论是double类型的加法还是float类型的加法,都可以使用这个模板完成加法的计算,这大大简化了代码量且非常简洁。正如这样:
#include <iostream>using namespace std;// 定义一个加法函数模板template<typename T>add(T a, T b) {	T sum = a + b;return sum;}intmain() {// int类型的加法int a = 10;int b = 20;int sum = add(a, b);   // 30	cout << "a + b = :" << sum << endl;        // float类型的加法float c = 1.8;float d = 2.2;float sum1 = add(c, d);   // 4.0	cout << "c + d = :" << sum1 << endl;        // double类型的加法double e = 3.1;double f = 2.5;double sum2 = add(e, f);   // 5.6	cout << "e + f = :" << sum2 << endl;        return 0;}
·template:即声明模板的关键字。
·typename/class:类型形参关键词。
·T:数据类型。

二:模板形参与模板实参

参考:https://zh.cppreference.com/cpp/language/template_parameters

模板以一个或多个模板形参参数化,形参有三种:类型模板形参、非类型模板形参和模板模板形参。

1:模板形参

模板形参:在模板定义中声明的占位符,可以是:

·类型形参(typename T 或 class T):代表一个未知类型。

·非类型形参(int N):代表一个具体的值(如整数、指针等)。

·模板形参(template <typename> class C):表示一个未知的类模板。

2:模板实参

模板实参:在实例化时传递给模板形参的具体类型或值。

示例程序如下:

#include<iostream>using namespace std;#include<vector>// 1. 类型模板形参: T// 2. 非类型模板形参: int N// 3. 模板模板形参: template<typename> class Containertemplate<typename T, int N, template<typenameclass Container>class MyData {private:    // 使用类型形参 T 和非类型形参 N	T data[N];    // 数组 data 用于存储 N 个类型为 T 的元素    // 使用模板模板形参 Container	Container<T> backup;   // 备份容器,用于存储类型为 T 的元素public:    voidsetValue(int index, T value)    {        if (index < N)         {            data[index] = value;  // 如果索引小于 N,将值存储在数组中        }    }    getValue(int index)    {        return data[index];    }};intmain(){    // 1. 类型实参: double    // 2. 非类型实参: 5 (一个具体的整数值)    // 3. 模板模板实参: std::vector    MyData<double5, std::vector> myObj;    myObj.setValue(03.14);    cout << "Value: " << myObj.getValue(0) << endl;    return 0;}

输出结果为:Value:3.14

三:函数模板

作用:定义一系列接受不同类型参数的函数。

示例程序如下:

#include<iostream>using namespace std;// 定义一个函数模板,用于交换两个变量的值template <typename T> // T 是类型形参voidmySwap(T& a, T& b){    T temp = a;    a = b;    b = temp;}intmain(){    int x = 1, y = 2;    mySwap(x, y); // 实例化为 mySwap<int>(int&, int&)    cout << "x = " << x << ", y = " << y << endl; // 输出: x=2, y=1    double u = 1.5, v = 2.5;    mySwap(u, v); // 实例化为 mySwap<double>(double&, double&)    cout << "u = " << u << ", v = " << v << endl; // 输出: u=2.5, v=1.5    return 0;}

输出结果为:

四:类模板

作用:定义一系列操作相同但数据类型不同的类。

示例程序如下:

#include<iostream>using namespace std;// 定义一个类模板,用于存储一个“盒子”template <typename T> // T 是类型形参class Box {private:    T content;public:    Box(T value) : content(value) {}    getContent()const    {        return content;    }};intmain(){    Box<intintBox(123);        // 实例化为存储 int 的盒子    Box<std::string> strBox("Hello")// 实例化为存储 string 的盒子    cout << intBox.getContent() << endl; // 输出: 123    cout << strBox.getContent() << endl; // 输出: Hello    return 0;}

输出结果为:123,Hello

五:别名模板

作用:为模板化的类型创建一个别名,简化代码。

示例程序如下:

#include<iostream>using namespace std;#include<vector>#include<string>// 为“存储 T 类型元素的向量”这个模板族起一个别名template <typename T>using Vec = std::vector<T>;         // 等同于 std::vector<T>// 为“存储字符串的向量”这个具体特化起一个别名using StrVec = Vec<std::string>;    // 等同于 std::vector<std::string>intmain(){    Vec<int> intVec;     // 等同于 std::vector<int>    StrVec strVec;       // 等同于 std::vector<std::string>    strVec.push_back("Hello");    cout << strVec[0] << endl;    cout << strVec.at(0) << endl;    return 0;}

输出结果为:Hello,Hello

六:变量模板

作用:定义一系列变量,例如数学常数在不同类型下的值。

示例程序如下:

#include<iostream>using namespace std;// 定义一个变量模板,表示π的值template <typename T> // T 是类型形参constexpr T pi = T(3.1415926535897932385L);intmain(){    cout << pi<float> << endl;   // 以 float 类型实例化,输出: 3.14159    cout << pi<double> << endl;  // 以 double 类型实例化,输出: 3.14159    cout << pi<int> << endl;     // 以 int 类型实例化,输出: 3    return 0;}

输出结果为:

七:约束与概念

作用:为模板形参添加编译期约束,使错误信息更清晰,并允许函数重载和模板特化基于类型特征。

示例程序如下:

#include<iostream>using namespace std;#include<concepts>// 包含标准概念// 定义一个概念:类型 T 必须是整数类型template <typename T>concept Integral = std::is_integral_v<T>;// 约束:函数 add 只接受满足 Integral 概念的类型template <Integral T> // T 是类型形参,受 Integral 约束add(T a, T b){    return a + b;}intmain(){    cout << add(12) << endl;   // 3,OK,int 满足 Integral    // cout << add(1.5, 2.5) << endl; // 错误!double 不满足 Integral,编译期报错    return 0;}

输出结果为:3

总结:

注意:

·模板并不是万能的,不要为了写模板而写模板。

·用好模板可以解决自定义类型的通用化。

八:奇异递归模板

参考:https://zh.cppreference.com/cpp/language/crtp

运行时多态(动态多态)使用传统继承+虚函数,会引入虚表(vtable),每次调用虚函数都要通过间接寻址,影响性能。

奇异递归模板模式(Curiously Recurring Template PatternCRTP)是一种C++编译期多态技术(静态多态)其核心思想为:把派生类作为基类的模板参数。CRTP就是让子类把自己作为模板参数传递给父类。

基本语法结构如下:

template <typename Derived>classBase{    // 基类定义};// 关键点:派生类将自己作为模板参数传递给基类classDerived : publicBase<Derived>{    // 派生类定义};

Derived继承自Base<Derived>,也就是说,子类 把自己“递归”地传给了父类。这就是“奇异递归”的由来。

CRTPC++中主要有两种用途:

·静态多态(static polymorphism)。

·添加方法,同时精简代码。

所谓静态多态,是指CRTP通过派生类对基类模板实例化,也可以实现类似动态多态的效果。最鲜明的特点就是:

1继承自模板类;

2使用派生类作为模板参数特化基类;

静态多态实现原理:编译时编译器会为模板生成一份实例化代码,根据对应实例调用对应函数。

静态多态与和动态的区别是:多态是动态绑定(运行时绑定)CRTP是静态绑定(编译时绑定)。

其中,动态多态在实现多态时,需要重写虚函数,这种运行时绑定的操作往往需要查找虚表等,效率低。而template的核心技术在于编译期多态机制,与运行期多态相比,这种机制提供编译期多态性,给了程序运行期无可比拟的效率优势。

示例程序如下:

#include<iostream>using namespace std;template<typename T>class Base {public:    voidRun()    {        static_cast<T*>(this)->Run();    }};class Derived : public Base<Derived> {public:    voidRun()    {        cout << "Derived::Run()" << endl;    }};intmain(){    Base<Derived>* ptr = new Derived();    ptr->Run();    return 0;}

输出结果为:Derived::Run()

使用派生类作为模板参数特化基类,这样做的目的是为了基类中使用派生类,从基类的角度来看,派生类其实也是基类,通过向下转换。因此,基类可以通过static_cast把其转换到派生类,从而使用派生类的成员,形式如下:

·T* derived = static_cast<T*>(this)

·T& derived = static_cast<T&>(*this)

这两种方式都是可以使用的。

注意:这里不使用dynamic_cast,因为dynamic_cast一般是为了确保在运行期向上向下转换的正确性。CRTP的设计是:派生类就是基类的模板参数,因此static_cast就可以了。