引言
在我们学习C++时,常会用到函数重载。而函数重载,通常会需要我们编写较为重复的代码,这就显得臃肿,且效率低下。重载的函数仅仅只是类型不同,代码的复用率比较低,只要有新类型出现时,就需要增加对应的函数。此外,代码的可维护性比较低,一个出错可能会导致所有的重载均出错。那么,模板的出现,就让这些问题有了解决方案。
Template
定义:C++的template是一个实体,它包含:
- 一系列函数(函数模板),可以是成员函数
- 一系列类(类模板),可以是嵌套类
- 类型系列的别名(别名模板)(从 C++ 11 起)
- 变量系列(变量模板)(从C++ 14 起)
1. 函数模板
我们知道,对一个C++函数,由于输入参数类型确定后,我们调用该函数只能传入与函数参数类型相同变量,如果我们想要传入其他类型的参数,就需要定义一个以该类型变量为参数的函数,例如,我们想要打印int和float值,就得写两个函数:
#include<iostream>
void intFunc(int a){
std::cout<<a<<std::endl;
}
void intFunc(float a){
std::cout<<a<<std::endl;
}
int main()
{
int a=2;
float b=3;
intFunc(b);
return 0;
}
此时我们便可以利用C++的模板来实现此功能
#include<iostream>
template<typename T>
void func(T a){
std::cout<<a<<std::endl;
}
int main()
{
int a=2;
float b=3;
func(a);
func(b);
return 0;
}
此时,我们看到,如果我们需要比较多个类型时,需要多个函数,而实际上我们只需要定义一个 “函数” 就能解决。而template 的核心就是:虽然我们没有指定类型,但是编译器会实现参数类型的自动推导。
2. 类模板
同样的,我们可以使用类模板来实现类的定义。比如,你需要一个类,在不用的平台上调用不同的函数,此时我们就可以用模板来实现这一功能。
头文件如下:
#include <iostream>
enum platform
{
unknow,
amd,
nvidia,
android,
apple
};
template <platform>
class test1
{
public:
void print();
};
在对应的cpp文件中,函数实现时,需要在函数上一行添加template<>来申明这个函数是模板类中的函数。
#include "template/template_test.h"
template<>
void test1<unknow>::print(){
std::cout<<"This is unknow platform!"<<std::endl;
}
template<>
void test1<amd>::print(){
std::cout<<"This is amd platform!"<<std::endl;
}
template<>
void test1<nvidia>::print(){
std::cout<<"This is nvidia platform!"<<std::endl;
}
在调用时,需要根据CMake中定义的变量来判断当前平台是什么,amd_support == 1表明当前是amd平台,main函数如下:
#if amd_support == 1
#define PLATFORM platform::amd
#elif nvidia_support == 1
#define PLATFORM platform::nvidia
#else
#define PLATFORM platform::unknow
#endif
int main()
{
test1<PLATFORM> obj;
obj.print();
return 0;
}
对应的CMake如下:
option(amd "amd platform" OFF)
option(nvidia "nvidia platform" OFF)
if(amd MATCHES "ON")
add_definitions(-Damd_support=1)
message("amd")
endif()
if(nvidia MATCHES "ON")
add_definitions(-Dnvidia_support=1)
message("nvidia")
endif()
然后编译
mkdir build && cd build
cmake -Damd=ON ..
make -j
运行结果如下:
如果在编译时不指定平台,即:cmake ..
此时执行结果就会变成unknown
3.别名模板
别名模板是C++的一种高级抽象机制,它允许我们为模板创建一个别名,使得我们在使用时可以更简洁、直观地表达复杂的模板类型。其基本语法形式如下:
template <typename T>
using MyAliasTemplate = SomeComplexTemplate<T>;
在这个例子中,MyAliasTemplate就是SomeComplexTemplate的一个别名,它们在使用时可以互换。
3.1 别名模板的应用实例
假设我们有一个用于表示向量的复杂模板类:
template <typename T, std::size_t N>
class Vector {
// ...
};
我们可以为其创建一个别名模板:
template <typename T, std::size_t N>
using Vec = Vector<T, N>;
这样一来,当我们需要声明一个三维浮点型向量时,原本可能需要这样写:
Vector<float, 3> myVec;
而现在通过别名模板,我们可以简化为:
Vec<float, 3> myVec;
3.2 别名模板的优势
1. 提高代码可读性:通过赋予模板一个更具描述性的名称,可以增强代码的自解释性,使其他开发者更容易理解代码含义。
2. 简化模板类型的使用:对于那些参数列表较长或复杂的模板类型,使用别名模板能够显著减少重复输入,提升编写和阅读效率。
3. 便于类型推导:在某些情况下,别名模板可以帮助编译器进行更好的类型推导,特别是在模板元编程中。
4.变量模板
在C++14之前,模板通常只应用于函数和类。而变量模板则允许我们将模板参数应用于非类型实体——变量,即定义一个可以根据模板参数生成不同具体类型的全局变量或静态数据成员。基本语法如下:
template<typename T>
constexpr T PI = T(3.1415926535897932385);
在这个例子中,PI就是一个变量模板,它可以接受任何类型的模板参数T,并根据T的不同生成对应的常量。
4.1 变量模板应用场景
1. 常数模板
例如,上述的圆周率模板可以方便我们在各种精度的数据类型上使用:
int main() {
cout << PI<int>; // 输出:3
cout << PI<double>; // 输出:约3.14159
}
2. 功能性变量模板
此外,变量模板还可以结合std::integral_constant等工具实现一些功能性变量,比如定义一个标志位模板:
template<bool B>
constexpr bool is_true = B;
int main() {
static_assert(is_true<true>, "True assertion should pass");
static_assert(!is_true<false>, "False assertion should fail");
}
4.2 变量模板的优势
1. 泛型编程的扩展:变量模板的引入进一步丰富了C++的泛型编程能力,使得不仅函数和类可以成为泛型的一部分,连变量也能参与到模板参数化的过程中来。
2. 简化代码与提高复用性:对于那些需要根据不同类型生成对应常量的情况,变量模板提供了一种简洁且高效的解决方案。
3. 利于编译期计算:由于变量模板常常与constexpr配合使用,因此可以在编译期就确定值,有利于编译期优化和静态断言的使用。
附录
https://github.com/xiaoycolor/cmakestudy/tree/main/src/template