当前位置:首页 » 《休闲阅读》 » 正文

C++模版进阶:为代码筑梦,为逻辑痴狂

16 人参与  2024年11月25日 10:01  分类 : 《休闲阅读》  评论

点击全文阅读


在这里插入图片描述

公主请阅

1.非类型模版参数2.模版的特化函数模版特化1. 基本概念2. 基本语法3. 示例代码4. 何时使用函数模板特化5. 注意事项6. 总结 类模版的特化1. 完全特化(Full Specialization)2. 部分特化(Partial Specialization)总结 3.模版分离编译解决方案一解决方案二总结为什么模板分离编译难实现模板分离编译的实现方法适用场景总结

1.非类型模版参数

模板参数分类类型形参与非类型形参。

类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。

非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常

量来使用

我们平时定义的都是类型的参数

但是我们这里的非类型模版参数定义是是整型常量

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>#include<vector>using namespace std;//#define N 10//非类型模版参数只能用整型,template<class T,size_t N=10>//非类型模版参数也可以给缺省值class Stack{private:T _a[N];int _top;};//#include<array>int main(){//如果使用宏的话我们让一个栈存20,一个栈存2000是做不到的//因为这个宏只能写死成一个//那么我们在模版里面加一个非类型模版参数就很好了Stack <int>st;//20Stack <int,20>st1;//20Stack <int,2000>st2;//2000//array<int,100>a1;vector<int> q1(100, 0);return 0;}

非类型模版参数只能用整型

2.模版的特化

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些

错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板

水白了就是特殊化处理对于某种类型,我们单独写一种模版进行处理,然后我们使用对应的类型的时候就会调用对应的模版了

函数模版特化

函数模板特化(Function Template Specialization)是C++中的一种特化模板的方式,它允许我们为特定的数据类型实现一个与通用模板不同的版本,从而进行优化或满足特殊需求。函数模板特化可以帮助处理特定类型的特殊情况,提供更灵活、清晰的代码结构。下面详细介绍它的定义、使用方法及示例。

1. 基本概念

在C++中,模板是一种泛型编程技术,允许你编写可以处理任意类型的函数或类。然而,有时你可能希望针对某些特殊类型提供不同的实现,函数模板特化可以满足这一需求。

函数模板特化是一种对特定类型进行特别实现的方法。通常可以这样理解:

通用模板:为所有类型提供的默认实现。

特化模板:针对某种特定类型的实现。

2. 基本语法

定义一个函数模板特化的语法如下:

template <typename T>void func(T param); // 通用模板template <>void func<int>(int param); // 针对int类型的特化模板

在这里,func<int>是对func模板的一个特化实现,专门用于int类型。这种特化版本只会在模板参数为int时被调用。

3. 示例代码

下面是一个使用函数模板特化的简单例子:

#include <iostream>using namespace std;template <typename T>void printValue(T value) {    cout << "General template: " << value << endl;}template <>void printValue<int>(int value) {  // 特化版本    cout << "Specialized template for int: " << value << endl;}int main() {    printValue(10);     // 会调用特化版本    printValue(3.14);   // 会调用通用模板    printValue("Hello"); // 会调用通用模板    return 0;}

输出结果

Specialized template for int: 10General template: 3.14General template: Hello

在这个例子中:

printValue(10);调用了int类型的特化版本。

printValue(3.14);printValue("Hello");则调用了通用模板。

4. 何时使用函数模板特化

函数模板特化通常在以下情况使用:

当某些类型需要特殊处理时,例如性能优化、格式化输出或对类型特征的不同处理。

当默认实现无法满足某些类型的需求时。

当某些特定类型的处理方式与其他类型完全不同时。

5. 注意事项

函数模板特化仅在特定类型参数被调用时生效。

函数模板特化在复杂的模板编程中可能会导致代码难以理解,所以要谨慎使用。

在函数模板特化时,必须明确指定具体的类型,不能使用部分特化(部分特化仅适用于类模板)。

6. 总结

函数模板特化是C++模板编程中的一种重要特性,它允许我们针对特定类型提供特殊实现,从而增强代码的灵活性和可读性。使用时要确保逻辑清晰,避免滥用特化导致代码混乱。

class Date{public:    Date(int year = 1900, int month = 1, int day = 1)        : _year(year)        , _month(month)        , _day(day)    {}    bool operator<(const Date& d)const    {        return (_year < d._year) ||            (_year == d._year && _month < d._month) ||            (_year == d._year && _month == d._month && _day < d._day);    }    bool operator>(const Date& d)const    {        return (_year > d._year) ||            (_year == d._year && _month > d._month) ||            (_year == d._year && _month == d._month && _day > d._day);    }    friend ostream& operator<<(ostream& _cout, const Date& d);private:    int _year;    int _month;    int _day;};//声明和定义分离ostream& operator<<(ostream& _cout, const Date& d){    _cout << d._year << "-" << d._month << "-" << d._day;    return _cout;}//函数模版template<class T>bool Less(const T& left, const T& right)//这里的const修饰的是left和right的{    return left < right;}//特化//template<>//这里的模版参数是空的,我们让T特化成Date**左边的const是修饰指向的内容的,右边的const是修饰指针本身的//bool Less<Date*>(Date*const& left, Date*const& right)//我们这里期望修饰的是指针本身//{ //    return *left < *right;//}//函数模版支持特化,但是挺麻烦的,我们没必要进行特化的操作的//普通函数和函数重载是可以同时存在的//当模版和普通函数同时存在的时候会调用普通函数的bool Less(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 = new Date(2023, 5, 2);    Date* p2 = new Date(2023, 4, 22);    cout << Less(p1, p2) << endl; //结果是随机的,因为是new出来的指针,带有随机性的    return 0;}

函数模版可以使用但是不推荐

类模版的特化

//类模版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; }};//偏特化template<class T1>class Data<T1,int>{public:    Data() { cout << "Data<T1, int>" << endl; }};//偏特化---特化部分参数//参数只要是指针的话就会匹配这个偏特化的template<typename T1,typename T2>class Data<T1*, T2*>{public:    Data() { cout << "Data<T1*, T2*>" << endl; }};//偏特化//参数也可以是引用,甚至指针和引用混在一起template<typename T1, typename T2>//--对类型的进一步限制class Data<T1&, T2&>{public:    Data() { cout << "Data<T1&, T2&>" << endl; }};int main(){    //只要第二个参数是int的话那么走的就是偏特化的操作    Data<int, int> d1;//走偏特化    Data<int, char> d2;//走全特化    Data<char, char>d3;//走的原模版    Data<char, int>d4;//走偏特化    //如果我们满足全特化和偏特化的话,那么应该走谁呢?    //编译器会走全特化的    Data<char*, int*>d5;//走偏特化    Data<int&, int&>d6;    return 0;}

在C++中,模板特化(Template Specialization)是一种允许我们为特定类型定义模板类或模板函数的机制。它允许我们通过为特定的类型提供不同的实现来优化代码。模板特化可以分为完全特化部分特化

1. 完全特化(Full Specialization)

完全特化是指为某个特定类型的模板参数提供完全不同的实现。例如,我们有一个模板类 MyClass,用于处理不同类型的数据,而对于 int 类型的数据,我们希望使用不同的实现:

template <typename T>class MyClass {public:    void printType() {        std::cout << "General template" << std::endl;    }};// 完全特化template <>class MyClass<int> {public:    void printType() {        std::cout << "Specialized for int" << std::endl;    }};

在使用时,MyClass<int> 会调用特化后的版本,而 MyClass<double> 等其他类型会调用通用模板:

MyClass<int> myInt;myInt.printType(); // 输出: Specialized for intMyClass<double> myDouble;myDouble.printType(); // 输出: General template

2. 部分特化(Partial Specialization)

部分特化用于类模板,允许我们为一些类型参数提供特化,而不要求完全指定所有的类型参数。部分特化无法用于函数模板,但可以用于类模板。例如,我们有一个包含两个模板参数的类 MyClass,我们希望在第二个参数为 int 时提供不同的实现:

template <typename T, typename U>class MyClass {public:    void printType() {        std::cout << "General template" << std::endl;    }};// 部分特化template <typename T>class MyClass<T, int> {public:    void printType() {        std::cout << "Specialized for second parameter as int" << std::endl;    }};

在使用时,当第二个模板参数为 int 时会调用部分特化版本,而其他组合则会调用通用模板:

MyClass<double, int> myDoubleInt;myDoubleInt.printType(); // 输出: Specialized for second parameter as intMyClass<double, double> myDoubleDouble;myDoubleDouble.printType(); // 输出: General template

总结

模板特化允许我们在模板编程中根据特定的类型提供优化的实现,使得代码更灵活。

//特化namespace kai{    template <>    struct less<Date*>    {        bool operator() (Date*const & x, Date* const& y)const        {            cout << "走的是Date*特化版本" << endl;            return *x < *y;                    }    };}int main(){    //kai::priority_queue<Date*,vector<Date*>,DateLess>q1;//q1是一个存储指针的对象    //q1 是一个 priority_queue 类型的对象    kai::priority_queue<Date*>q1;    q1.push(new Date{ 2024,10,23 });//里面的new就是初始化一个Date对象并进行时间的初始化操作    q1.push(new Date{ 2024,5,27 });    q1.push(new Date{ 2024,11,2 });    while (!q1.empty())    {        cout << *q1.top() << " ";        q1.pop();    }    cout << endl;    return 0;}

3.模版分离编译

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有

目标文件链接起来形成单一的可执行文件的过程称为分离编译模式

普通函数是可以声明和定义进行分离的,但是我们的模版是不行的

说白了这个就是模版专有的问题

编译链接的过程

1.预处理:展开头文件,条件编译,宏替换,去掉注释,生成了a.i 和test.i文件

2.编译:检查语法,生成汇编代码,生成a.s和test.s文件

3.汇编:把汇编代码转换成二进制的机器码,生成了a.o和test.o文件,就是目标文件

4.链接:合并在一起,并且没有确定地址的函数,要确定地址等工作,生成可执行程序 a.out

在这里插入图片描述
在这里插入图片描述
为什么链接时,Func可以链接成功,Add无法连接成功

test.i中知道Add需要实例话成int和double各一份,但是他只有是声明没有定义

a.i中知道有Add的定义,但是不知道要实例化模版成什么类型

链接的时候找不到模版的地址

解决方案一

那么我们怎么解决呢?

我们可以通过显示实例化进行问题的解决了

在这里插入图片描述
在这里插入图片描述
但是显示实例化存在缺陷的

但是我们用一个新类型就得增加一个实例化了

所以这个方法不实用

解决方案二

不要声明和定义分离到两个文件

我们直接定义在.h文件就行了,都不用声明了

总结

模板分离编译(Template Separate Compilation)是C++中处理模板代码的一种编译技术,目的是减少模板的编译时间和加快编译速度。在C++中,模板的实现和使用常常导致代码膨胀和编译时间增加。分离编译可以帮助解决这些问题,但在C++中实现分离编译的难度较大,因为C++模板的编译方式与普通函数和类不同。

为什么模板分离编译难实现

C++模板的编译与普通代码不同,主要体现在以下几点:

编译时实例化:模板是在使用时(而非声明时)进行实例化的,编译器需要知道模板的具体类型,以便生成实际代码。

需要源代码可见性:因为模板实例化需要看到模板的实现,所以通常需要将模板定义和实现放在同一个头文件中,而不是将实现放在源文件中。

不完全支持分离编译:标准C++并不完全支持模板的分离编译,因此在模板实现和使用分离时,可能会导致链接错误。

模板分离编译的实现方法

尽管C++标准本身并不直接支持模板的分离编译,但是可以使用一些变通的方式来实现:

显式实例化(Explicit Instantiation)

在实现文件(.cpp)中显式实例化某些特定类型的模板。这可以让编译器提前生成模板的代码,而不需要在头文件中包含模板的实现代码。

示例:

// MyTemplate.h
template
class MyTemplate {
public:
void doSomething();
};

// MyTemplate.cpp
#include “MyTemplate.h”
template
void MyTemplate::doSomething() {
// 模板的实现
}

// 显式实例化特定类型的模板
template class MyTemplate;
template class MyTemplate;

  - 这样可以避免模板的多次实例化,减少编译时间,但仅适用于已知的特定类型。5. **将实现放在头文件中**:  - 将模板类的实现直接放在头文件中,以便在编译时直接看到模板的实现,进行实例化。虽然这不是真正的分离编译,但它避免了链接错误。  - 示例:    ```C++// MyTemplate.htemplate <typename T>class MyTemplate {public:    void doSomething() {        // 模板实现直接写在头文件中    }};
使用inlineextern关键字

通过将模板函数定义为inline,可以让编译器在多个翻译单元中避免重复实例化。

在某些编译器中,extern template可以声明但不定义模板实例化,允许分离编译,减少重复实例化,但这并非标准化的做法,依赖于编译器的支持。

模块化C++: C++20引入的模块特性可以更好地支持模板的分离编译。不过,模块化C++需要现代的编译器支持,目前的生态还在完善中。

适用场景

代码复用高的模板库:如果是一个通用的模板库,分离编译能够显著加快用户的编译速度,减少冗余编译。

大型项目:在大型项目中,减少模板的重复实例化可以降低编译时间,提升开发效率。

总结

模板分离编译在C++中不是一个完全支持的特性,需要借助显式实例化、将实现放在头文件中或其他非标准化的方法来实现。随着C++20模块特性的推广,未来模板分离编译可能会变得更加普遍。


点击全文阅读


本文链接:http://zhangshiyu.com/post/191637.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1