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

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_H

template <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.kler.cn/news/341733.html

相关文章:

  • 人检测-目标检测数据集(包括VOC格式、YOLO格式)
  • 每日C#语法题
  • 5.toString()、构造方法、垃圾回收、静态变量与静态方法、单例设计模式、内部类
  • 大贤3D家谱-一键寻找家谱本源
  • 逼近理论及应用精解【11】
  • 11.1 Linux_线程_线程相关函数
  • 多模态方法总结
  • OpenStack Yoga版安装笔记(十六)Openstack网络理解
  • Nuxt.js 应用中的 page:transition:finish 钩子详解
  • Linux系统——硬盘操作
  • 【通过WSL2安装Ubuntu24.04系统及图形化界面】
  • 代码随想录算法训练营Day19
  • 跨境电商独立站||代码建站和SaaS建站的区别
  • 毕业设计 大数据电影数据分析与可视化系统
  • 前端框架选择指南
  • C语言函数栈帧的创建与销毁(32)
  • vue2中组件注册后,调用时如何命名?组件传参时参数名称如何命名?
  • Redis 排行榜:实现、操作与性能优化
  • 使用 Vue 官方脚手架初始化 Vue3 项目
  • 【MySQL】入门篇—数据库基础:数据库的定义与用途