c++ 模板 template
C++ 的模板一直是这门语言的一种特殊的艺术,模板甚至可以独立作为一门新的语言来进行使用。模板的哲学在于将一切能够在编译期处理的问题丢到编译期进行处理,仅在运行时处理那些最核心的动态服务,进而大幅优化运行期的性能。因此模板也被很多人视作 C++ 的黑魔法之一。
首先,c++模板中的两个关键字typename和class其含义是相同的,为什么两者均可用且相同是因为 Stan Lippman 曾在其博客中表示,最早 Stroustrup 使用 class
来声明模板参数列表中的类型是为了避免增加不必要的关键字;后来委员会认为这样混用可能造成概念上的混淆才加上了 typename
关键字。
而使用 typename 的作用就是告诉 c++ 编译器,typename 后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有 typename,编译器没有任何办法知道 T::LengthType 是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过。
对于用于模板定义的依赖于模板参数的名称,只有在实例化的参数中存在这个类型名,或者这个名称前使用了 typename 关键字来修饰,编译器才会将该名称当成是类型。除了以上这两种情况,绝不会被当成是类型。
因此,如果你想直接告诉编译器T::const_iterator是类型而不是变量,只需用 typename修饰:
template<typename T> void fun(const T& proto ,typename T::const_iterator it);
上例中fun的第二个参数it是依赖于T的一个T::const_iterator类型(T的迭代器)的值。模板中依赖于模板参数的名称称为从属名称(dependent name), 当一个从属名称嵌套在一个类里面时,称为嵌套从属名称(nested dependent name)。 其实T::const_iterator还是一个嵌套从属类型名称(nested dependent type name)。
使用模板时我们可用去显式指定模板参数,也可以让编译器自动推导:
template<typename T1, typename T2, typename T3> T1 sum(T2 t2, T3 t3) { return t2 + t3; } sum<long long>(10,200); //or sum<long long, int, int>();
但需注意自动推导时需保证输入的参数之间能满足一些运算符操作或安全的隐式转换。
对于模板,只有在使用时才会实例化,但是虽然在未实例化之前,编译器没有生成具体的代码,但我们仍然可以进行函数指针绑定的操作。
template <typename T> int compare(const T & t1, const T & t2) { } int (*pf_int)(const int &,const int &) = compare; //此处需显示的写出模板参数
同样的我们也可以将模板函数作为回调函数进行传参,但此时可能会产生二义性,所以注意显示的写出模板参数。
模板特化
函数模板特化:
如果有些特殊类型无法使用模板去泛型编程,那么就要用模板特化去单独实现,举例如下:
template<typename T> bool IsEqual(T a T b){ return a==b; } template<> bool IsEqual(char *a char *b){ if(strcmp(a,b)==0)return true; return false; }
上例定义了一个函数模板IsEqual,因为对于char*类型对象的比较不能直接用==,因此需要针对特定类型数据进行函数模板特化。
上面的template<>无参数,是完全特化,如果没有template则是普通函数重载,需注意c++中对于重载函数,普通函数优先级最高,其次是模板函数。
类模板特化:
类模板的完全特化同函数模板,不再赘述,但类模板中还有一种特殊特化---偏特化,即模板中保留固定类型参数(需注意,函数模板不支持偏特化)
template<typename T1,typename T2> //普通类模板 class test{ ... }; template<typename T1,typename T2> class test<T1*,T2*>{ //偏特化(针对普通指针) ... }; template<typename T1,typename T2> class test<const T1*,T2*>{ //偏特化(针对const指针) ... }; //例如c++标准库中的类vector的定义 template <class T, class Allocator> class vector { // … // }; template <class Allocator> //这个偏特化的例子中,一个参数被绑定到bool类型,而另一个参数仍未绑定需要由用户指定。 class vector<bool, Allocator> { //…//};
变长参数模板
当参数个数无法确定时,可以使用变长参数模板,使用样例如下:
template<typename... Ts> class Magic; //确定参数个数0 template<typename Require, typename... Args> class Magic; //确定参数个数1 template<typename... Args> void printf(const std::string &str, Args... args); //变长参数模板函数
对于变长参数的解包,我们可以使用sizeof...
来计算参数的个数
template<typename... Ts> void magic(Ts... args) { std::cout << sizeof...(args) << std::endl; }
其次,对参数进行解包,到目前为止还没有一种简单的方法能够处理参数包,但有两种经典的处理手法:
1. 递归模板函数
递归是非常容易想到的一种手段,也是最经典的处理方法。这种方法不断递归地向函数传递模板参数,进而达到递归遍历所有模板参数的目的:
template<typename T0> void printf1(T0 value) { std::cout << value << std::endl; } template<typename T, typename... Ts> void printf1(T value, Ts... args) { std::cout << value << std::endl; printf1(args...); }
2. 变参模板展开
在 C++17 中增加了变参模板展开的支持,于是你可以在一个函数中完成 printf
的编写:
template<typename T0, typename... T> void printf2(T0 t0, T... t) { std::cout << t0 << std::endl; if constexpr (sizeof...(t) > 0) printf2(t...); }
非类型模板参数推导
前面我们主要提及的是模板参数的一种形式:类型模板参数---模板参数替换不同的参数类型
而还有一种模板可以让不同字面量成为模板参数,即非类型模板参数,如下:
template <typename T, int BufSize> class buffer_t { public: T& alloc(); void free(T& item); private: T data[BufSize]; } buffer_t<int, 100> buf; // 100 作为模板参数
上述代码中模板参数int BufSize此处指明了类型,可以当做consterpr int型直接去使用,而使用模板时将一个具体值作为模板参数。
此处我们用100作为模板参数传递,在c++17中允许使用auto关键字让编译器辅助完成类型推导:
template <auto value> void foo() { std::cout << value << std::endl; return; } int main() { foo<10>(); // value 被推导为 int 类型 }
外部模板
传统 C++ 中,模板只有在使用时才会被编译器实例化。换句话说,只要在每个编译单元(文件)中编译的代码中遇到了被完整定义的模板,都会实例化。这就产生了重复实例化而导致的编译时间的增加。并且,我们没有办法通知编译器不要触发模板的实例化。
为此,C++11 引入了外部模板,扩充了原来的强制编译器在特定位置实例化模板的语法,使我们能够显式的通知编译器何时进行模板的实例化:
template class std::vector<bool>; // 强行实例化 extern template class std::vector<double>; // 不在该当前编译文件中实例化模板
类型别名模板
对于模板来说,无法直接用typedef去为一个模板起别名,但是c++11引入了using,可以对模板起别名,如下
template<typename T, typename U> class MagicType { public: T dark; U magic; }; // 不合法 template<typename T> typedef MagicType<std::vector<T>, std::string> FakeDarkMagic; //合法 typedef int (*process)(void *); using NewProcess = int(*)(void *); template<typename T> using TrueDarkMagic = MagicType<std::vector<T>, std::string>;
参考文章:https://changkun.de/modern-cpp/zh-cn/02-usability/#%E7%B1%BB%E5%9E%8B%E5%88%AB%E5%90%8D%E6%A8%A1%E6%9D%BF
https://blog.csdn.net/lyn631579741/article/details/110730145
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探