当前位置: 首页 > news >正文

240604 模板进阶

模板进阶

1. 类模板的非类型模板参数

模板参数分为:
(1) 类型形参:出现在模板参数列表中,跟在class或typename之类的参数类型名称
(2)非类型形参:用一个常量作为类(函数)模板的一个参数,在模板中可将其当作常量使用

假设我们想要创建一个具有固定大小的栈,可以使用非类型模板参数来指定栈的最大尺寸:

#include <array>template <typename T, std::size_t Maxsize>
class Stack {
private:std::array<T, Maxsize> elems; // 元素std::size_t numElems;         // 当前元素数量public:Stack() : numElems(0) {}void push(T const &elem); // 压入元素void pop();               // 弹出元素T const &top() const;     // 返回顶部元素bool empty() const { return numElems == 0; } // 是否为空std::size_t size() const { return numElems; } // 当前元素数量
};// 实现 push 方法
template <typename T, std::size_t Maxsize>
void Stack<T, Maxsize>::push(T const &elem) {assert(numElems < Maxsize);elems[numElems] = elem;++numElems;
}

类模板没有实例化时,编译器不会去里面查细节,无法确定里面是类型还是静态变量
typename明确告诉是类型

template<class T>
void PrintVector(const vector<T>& v) {typename vector<T>::const_iterator it = v.begin();while (it != v.end()) {cout << *it << " ";++it;}cout << endl;
}

或者直接auto it = v.begin()

2. 函数模板的专用化/特化

仅作了解,不太实用

原模板:

template<class T>
bool Less(const T& l, const T& r){return l < r;
}

以日期类举例,原模板专用化后:

template<>
bool Less<Date*>(Date* const& l, Date* const& r) {return *l < *r;
}

其他类型都正常走原模板,Date* 走专用化的模板
坑多,一般不建议使用函数模板的专用化

推荐做法

template<class T>
bool Less(T l, T r){return l < r;
}bool Less(Date* l, Date* r){return *l < *r;
}

3. 类模板的特化

如果不使用函数模板的特化,更推荐的做法是使用函数重载来处理不同类型的情况。函数重载比模板特化更直观和灵活,在类型明确的情况下可以更好地表达意图。可以通过为特定类型(如Date*)编写专门的重载函数,而不需要对模板进行专门化。
(1)全特化

template<class T1, class T2>
class Show {
public:Show() {cout << "Show<T1, T2>" << endl;}
private:T1 _a;T2 _b;
};
// 类模板的特化:
template<>
class Show<int, char> {
public:Show() {cout << "Show<int, char>" << endl;}
};

(2)部分特化示例1

// 偏特化:特化部分参数
template<class T1>
class Show<T1, int> {
public:Show() {cout << "Show<T1, int>" << endl;}
private:T1 _a;int _b;
};// 偏特化:限定模板类型
// 涉及指针走下面,T1和T2对应的是原类型(不是指针)
template<typename T1, typename T2>
class Show<T1*, T2*> {
public:Show() {cout << "Show<T1*, T2*>" << endl;}
};

(3)部分特化示例2
实现自定义的比较器,用于指针类型之间的比较。
通常用于需要比较两个对象(通过指针)的场合:

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

在不同的情况下,T 就是指针所指向的具体类型。例如:
Date* 对应 T = Date
int* 对应 T = int
std::string* 对应 T = std::string

部分特化/偏特化 应用较多

4. 模板分离编译的问题

在C++中,当你在头文件中声明一个模板函数,并在源文件(.cpp文件)中实现它时,可能会遇到模板函数无法成功调用的问题。以下是原因及其解释:

(1)原因分析

  • 模板的实例化要求:模板函数需要在调用时能够看到其完整的定义。由于模板是基于类型参数生成的,每次使用模板时,编译器都需要知道模板的具体实现。如果实现放在 .cpp 文件中,而没有显式地让编译器知道该模板的定义,就会导致编译错误。

  • 普通函数的处理:普通函数(非模板函数)的定义在 .cpp 文件中,编译器在链接时能够找到它的实现,因此可以被成功调用。

(2)示例代码

假设有以下的文件结构:

func.h

#ifndef FUNC_H
#define FUNC_Htemplate <typename T>
void templateFunction(T value);  // 模板函数的声明void regularFunction();  // 普通函数的声明#endif

func.cpp

#include "func.h"
#include <iostream>template <typename T>
void templateFunction(T value) {  // 模板函数的实现std::cout << "Template value: " << value << std::endl;
}void regularFunction() {  // 普通函数的实现std::cout << "Regular function called." << std::endl;
}// 显式实例化
template void templateFunction<int>(int);  // 显式实例化模板

test.cpp

#include "func.h"int main() {regularFunction();  // 调用普通函数templateFunction(10);  // 调用模板函数return 0;
}

(3)解决方案

要使模板函数在 test.cpp 中成功调用,通常有以下几种方法:

  • 将模板函数的实现放在头文件中
    templateFunction 的实现直接放在 func.h 文件中,这样每次包含 func.h 时都会可见。

    template <typename T>
    void templateFunction(T value) {  // 模板函数的实现std::cout << "Template value: " << value << std::endl;
    }
    
  • 显式实例化很被动,需要不断添加显式实例,具有局限性,不方便
    func.cpp 中使用显式实例化声明,例如 template void templateFunction<int>(int);。这样,编译器在编译 func.cpp 时会生成针对特定类型的代码。

  • 在调用前包含实现
    通过在 test.cpp 中包含 func.cpp,确保模板的实现可用。但这不是推荐的做法,因为它会导致代码的重复编译。

(4)总结

  • 模板函数必须在调用时是可见的。为了避免这种问题,建议将模板的实现放在头文件中,或者使用显式实例化。
  • 普通函数因为有具体的实现和链接,因此在 test.cpp 中可以直接调用。

优点:本质上是把活交给编译器
a. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
b. 增强了代码的灵活性。
缺点
a. 模板会导致代码膨胀问题,也会导致编译时间变长。
b. 出现模板编译错误时,错误信息非常凌乱,不易定位错误。


http://www.mrgr.cn/news/47334.html

相关文章:

  • 每日一题 391. 完美矩形
  • leetcode热题100——NO.206反转链表——JAVA
  • leetcode热门100题1-4
  • 深度学习从入门到实战——卷积神经网络原理解析及其应用
  • shell脚本练习
  • 《废品机械师抢先版》V0.7.3.b776官方中文学习版
  • 机器学习:情感分析的原理、应用场景及优缺点介绍
  • 陪诊小程序搭建:打造便利的陪诊环境
  • vue 入门二
  • 23523423
  • 2024下半年软考中级软件设计师,这100题,必做!
  • C# Winform截图指定控件范围内的图像
  • 【AI知识点】召回率、精确率、准确率、F1-score和混淆矩阵
  • vc++(vs2010)-windows编程与绘图程序设计
  • 为什么numpy.array的数据像是字典一样,但是这个数据有real属性,又无法读取shape,显示0-d array
  • Linux——echo-tail-重定向符
  • 【部署分布式数据库DBMS】
  • ipguard与Ping32在各行业防数据泄漏方案大对比(企业必看)
  • 仅将 APO 用作采集存储展示 Trace 数据工具
  • 思科WLC使用Smart License
  • 《机器学习与神经网络:跨学科的突破与未来展望》
  • WordPress最佳恶意软件扫描插件:进阶级指南
  • 比较三组迭代次数的变化
  • Unite Shanghai 2024 技术专场 | Unity 6及未来规划:Unity引擎和服务路线图
  • Python即时获取上证指数信息并发送邮件到指定邮箱
  • 【大模型开发】AI提示词框架:解锁ICIO、CRISPE、BROKE和RASCEF的强大潜力