C++模板
模板的实参在任何时候都可以省略
类模板与模板类所指的是同一概念
类模板的参数必须是虚拟类型的
类模板中的成员函数全部都是模板函数
1)从模板函数实参表获得的信息有矛盾之处。
2)需要获得特定类型的返回值,而不管参数的类型如何。
3)虚拟类型参数没有出现在模板函数的形参表中。
4)函数模板含有常规形参。
1)从模板函数实参表获得的信息有矛盾之处。
2)需要获得特定类型的返回值,而不管参数的类型如何。
3)虚拟类型参数没有出现在模板函数的形参表中。
4)函数模板含有常规形参。
B:类模板与模板类的概念
⑴ 什么是类模板 一个类模板(也称为类属类或类生成类)允许用户为类定义一种模式,使得类中的某些数据成员、默写成员函数的参数、某些成员函数的返回值,能够取任意类型(包括系统预定义的和用户自定义的)。
如果一个类中数据成员的数据类型不能确定,或者是某个成员函数的参数或返回值的类型不能确定,就必须将此类声明为模板,它的存在不是代表一个具体的、实际的类,而是代表着一类类。
第一种情况:template<typename T> void fun(T const& a,T const& b);但是你调用时却是fun(250,250.4);那你就必须写成fun<int>(250,250.4);
第二种情况:template<typename T,typename RT> RT fun(T const& a,T const& b);
此时没有办法进行演绎,所以你可以重写template<typename RT,typename T> RT fun(T const& a,T const& b);调用时写成fun<double>(12,13);
非类型形参,指的是模板中的模板形参不是使用class关键字定义的,而是使用C++内置类型定义的形参,比如template<class T, int a> class B{},
其中的形参a就是非类型形参,他是使用的内置类型int声明的。
归纳以上的介绍,可以这样声明和使用类模板:
1) 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
2) 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
3) 在类声明前面加入一行,格式为:
template <class 虚拟类型参数>
如:
template <class numtype> //注意本行末尾无分号
class Compare
{…}; //类体
4) 用类模板定义对象时用以下形式:
类模板名<实际类型名> 对象名;
类模板名<实际类型名> 对象名(实参表列);
如:
Compare<int> cmp;
Compare<int> cmp(3,7);
5) 如果在类模板外定义成员函数,应写成类模板形式:
template <class 虚拟类型参数>
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
关于类模板的几点说明:
1) 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:
template <class T1,class T2>
class someclass
{…};
在定义对象时分别代入实际的类型名,如:
someclass<int,double> obj;
2) 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。
3) 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。有关这方面的知识实际应用较少,本教程暂不作介绍,感兴趣的同学可以自行学习。
可以参考《C++ Templates》中文版[美]David Vandevoorder Nicolai M.Josuttis
2019-03-06
模板为什么要特化,因为编译器认为,对于特定的类型,如果你对某一功能有更好地实现,那么就该听你的。
模板分为类模板与函数模板,特化分为全特化与偏特化。全特化就是限定死模板实现的具体类型,偏特化就是模板如果有多个类型,那么就只限定为其中的
一部分,其实特化细分为范围上的偏特化与个数上的偏特化。
模板的泛化:是指用的时候指定类型。
上面的方框内的内容是指模板的泛化,下面的方框内的内容是指模板的特化。特化的优先级比泛化的优先级高。
模板的偏(范围)特化是指个数,范围上的偏特化
个数上的偏特化,从左到右
测试代码如下:
#include <iostream> using namespace std; template<typename T1,typename T2> class Test{ public: Test(T1 i,T2 j):a(i),b(j){cout<<"模板类"<<endl;} private: T1 a; T2 b; }; template<> //全特化,由于是全特化,参数都指定了,参数列表故为空。 class Test<int ,char>{ public: Test(int i,char j):a(i),b(j){cout<<"全特化"<<endl;} private: int a; int b; }; template<typename T2> //由于只指定了一部分参数,剩下的未指定的需在参数列表中,否则报错。 class Test<char,T2>{ public: Test(char i,T2 j):a(j),b(j){cout<<"个数偏特化"<<endl;} private: char a; T2 b; }; template<typename T1,typename T2> //这是范围上的偏特化 class Test<T1*,T2*>{ public: Test(T1* i,T2* j):a(i),b(j){cout<<"指针偏特化"<<endl;} private: T1* a; T2* b; }; template<typename T1,typename T2>//同理这也是范围上的偏特化 class Test<T1 const,T2 const>{ public: Test(T1 i,T2 j):a(i),b(j){cout<<"const偏特化"<<endl;} private: T1 a; T2 b; }; int main() { int a; Test<double,double> t1(0.1,0.2); Test<int,char> t2(1,'A'); Test<char,bool> t3('A',true); Test<int*,int*> t4(&a,&a); Test<const int,const int> t5(1,2); return 0; }
运行结果截图:
而对于函数模板,却只有全特化,不能偏特化(否则与函数重载冲突):
#include <iostream> using namespace std; //模板函数 template<typename T1,typename T2> void fun(T1 a,T2 b){ cout<<"模板函数"<<endl; } //全特化 template<> void fun(int a,char b){ cout<<"全特化"<<endl; } //函数不存在偏特化,以下代码是错误的 /* template<typename T2> void fun(char a,T2 b){ cout<<"偏特化"<<ednl; } */ int main() { int a=0; char b='A'; fun(a,a); fun(a,b); return 0; }
运行截图如下:
c++模板特化偏特化
2019-03-31 16:18:59
C++11:可变参数的模板
概述
在C++11之前,类模板和函数模板只能含有固定数量的模板参数。C++11增强了模板功能,允许模板定义中包含0到任意个模板参数,这就是可变参数模板。
可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号“…”:
template<class ... T> void func(T ... args)//T叫模板参数包,args叫函数参数包
{//可变参数模板函数
}
func(); // OK:args不含有任何实参
func(1); // OK:args含有一个实参:int
func(2, 1.0); // OK:args含有两个实参int和double
T叫模板参数包,args叫函数参数包。
省略号“…”的作用有两个:
声明一个参数包,这个参数包中可以包含0到任意个模板参数
在模板定义的右边,可以将参数包展开成一个一个独立的参数
可变参数模板函数
可变参数模板函数的定义
一个可变参数模板函数的定义如下:
#include <iostream> using namespace std; template<class ... T> void func(T ... args) {//可变参数模板函数 //sizeof...(sizeof后面有3个小点)计算变参个数 cout << "num = " << sizeof...(args) << endl; } int main() { func(); // num = 0 func(1); // num = 1 func(2, 1.0); // num = 2 return 0; }
运行结果如下:
参数包的展开
递归方式展开
通过递归函数展开参数包,需要提供一个参数包展开的函数和一个递归终止函数。
#include <iostream> using namespace std; //递归终止函数 void debug() { cout << "empty\n"; } //展开函数 template <class T, class ... Args> void debug(T first, Args ... last) { cout << "parameter " << first << endl; debug(last...); } int main() { debug(1, 2, 3, 4); return 0; }
运行结果如下:
递归调用过程如下:
debug(1, 2, 3, 4);
debug(2, 3, 4);
debug(3, 4);
debug(4);
debug();
通过可变参数模板实现打印函数:
#include <iostream> #include <stdexcept> using namespace std; void Debug(const char* s) { while (*s) { if (*s == '%' && *++s != '%') { throw runtime_error("invalid format string: missing arguments"); } cout << *s++; } } template<typename T, typename... Args> void Debug(const char* s, T value, Args... args) { while (*s) { if (*s == '%' && *++s != '%') { cout << value; return Debug(++s, args...); } cout << *s++; } throw runtime_error("extra arguments provided to Debug"); } int main() { Debug("a = %d, b = %c, c = %s\n", 250, 'm', "mike"); return 0; }
运行结果如下:
非递归方式展开
#include <iostream> using namespace std; template <class T> void print(T arg) { cout << arg << endl; } template <class ... Args> void expand(Args ... args) { int a[] = { (print(args), 0)... }; } int main() { expand(1, 2, 3, 4); return 0; }
运行结果如下:
expand函数的逗号表达式:(print(args), 0), 也是按照这个执行顺序,先执行print(args),再得到逗号表达式的结果0。
同时,通过初始化列表来初始化一个变长数组,{ (print(args), 0)… }将会展开成( (print(args1), 0), (print(args2), 0), (print(args3), 0), etc…), 最终会创建一个元素只都为0的数组int a[sizeof…(args)]。
可变参数模板类
继承方式展开参数包
可变参数模板类的展开一般需要定义2 ~ 3个类,包含类声明和特化的模板类:
#include <iostream> #include <typeinfo> using namespace std; template<typename... A> class BMW{}; // 变长模板的声明 template<typename Head, typename... Tail> // 递归的偏特化定义 class BMW<Head, Tail...> : public BMW<Tail...> {//当实例化对象时,则会引起基类的递归构造 public: BMW() { printf("type: %s\n", typeid(Head).name()); } Head head; }; template<> class BMW<>{}; // 边界条件 int main() { BMW<int, char, float> car; return 0; }
运行结果如下:
模板递归和特化方式展开参数包
#include <iostream> using namespace std; template <long... nums> struct Multiply;// 变长模板的声明 template <long first, long... last> struct Multiply<first, last...> // 变长模板类 { static const long val = first * Multiply<last...>::val; }; template<> struct Multiply<> // 边界条件 { static const long val = 1; }; int main() { cout << Multiply<2, 3, 4, 5>::val << endl; // 120 return 0; }
运行结果如下:
深入应用C++11 代码优化与工程级应用
Mike__Jiang
可变参数模板消除重复代码
C++11之前如果要写一个泛化的工厂函数,这个工厂函数能接受任意类型的入参,并且参数个数要能满足大部分的应用需求的话,我们不得不定义很多重复的模版定义,比如下面的代码:
template<typename T> T* Instance() { return new T(); } template<typename T, typename T0> T* Instance(T0 arg0) { return new T(arg0); } template<typename T, typename T0, typename T1> T* Instance(T0 arg0, T1 arg1) { return new T(arg0, arg1); } template<typename T, typename T0, typename T1, typename T2> T* Instance(T0 arg0, T1 arg1, T2 arg2) { return new T(arg0, arg1, arg2); } template<typename T, typename T0, typename T1, typename T2, typename T3> T* Instance(T0 arg0, T1 arg1, T2 arg2, T3 arg3) { return new T(arg0, arg1, arg2, arg3); } template<typename T, typename T0, typename T1, typename T2, typename T3, typename T4> T* Instance(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { return new T(arg0, arg1, arg2, arg3, arg4); } struct A { A(int){} }; struct B { B(int,double){} }; A* pa = Instance<A>(1); B* pb = Instance<B>(1,2);
可以看到这个泛型工厂函数存在大量的重复的模板定义,并且限定了模板参数。用可变模板参数可以消除重复,同时去掉参数个数的限制,代码很简洁, 通过可变参数模版优化后的工厂函数如下:
template<typename… Args> T* Instance(Args&&… args) { return new T(std::forward<Args>(args)…); } A* pa = Instance<A>(1); B* pb = Instance<B>(1,2);
4. 可变参数模板实现泛化的delegate
C++中没有类似C#的委托,我们可以借助可变模版参数来实现一个。C#中的委托的基本用法是这样的:
delegate int AggregateDelegate(int x, int y);//声明委托类型 int Add(int x, int y){return x+y;} int Sub(int x, int y){return x-y;} AggregateDelegate add = Add; add(1,2);//调用委托对象求和 AggregateDelegate sub = Sub; sub(2,1);// 调用委托对象相减
C#中的委托的使用需要先定义一个委托类型,这个委托类型不能泛化,即委托类型一旦声明之后就不能再用来接受其它类型的函数了,比如这样用:
int Fun(int x, int y, int z){return x+y+z;}
int Fun1(string s, string r){return s.Length+r.Length; }
AggregateDelegate fun = Fun; //编译报错,只能赋值相同类型的函数
AggregateDelegate fun1 = Fun1;//编译报错,参数类型不匹配
这里不能泛化的原因是声明委托类型的时候就限定了参数类型和个数,在C++11里不存在这个问题了,因为有了可变模版参数,它就代表了任意类型和个数的参数了,下面让我们来看一下如何实现一个功能更加泛化的C++版本的委托(这里为了简单起见只处理成员函数的情况,并且忽略const、volatile和const volatile成员函数的处理)。
template <class T, class R, typename... Args> class MyDelegate { public: MyDelegate(T* t, R (T::*f)(Args...) ):m_t(t),m_f(f) {} R operator()(Args&&... args) { return (m_t->*m_f)(std::forward<Args>(args) ...); } private: T* m_t; R (T::*m_f)(Args...); }; template <class T, class R, typename... Args> MyDelegate<T, R, Args...> CreateDelegate(T* t, R (T::*f)(Args...)) { return MyDelegate<T, R, Args...>(t, f); } struct A { void Fun(int i){cout<<i<<endl;} void Fun1(int i, double j){cout<<i+j<<endl;} }; int main() { A a; auto d = CreateDelegate(&a, &A::Fun); //创建委托 d(1); //调用委托,将输出1 auto d1 = CreateDelegate(&a, &A::Fun1); //创建委托 d1(1, 2.5); //调用委托,将输出3.5 }
MyDelegate实现的关键是内部定义了一个能接受任意类型和个数参数的“万能函数”:R (T::*m_f)(Args…),正是由于可变模版参数的特性,所以我们才能够让这个m_f接受任意参数。
SFINAE即替换失败不是错误(Substitution Failure Is Not An Error),其作用是当我们在进行模板特化的时候,会去选择那个正确的模板,避免失败。在函数模板的重载决议中应用此规则:当将模板形参替换为显式指定的类型或推导的类型失败时,从重载集中丢弃这个特化,而非导致编译失败。此特性被广泛用于模板元编程。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2015-10-01 exit与_exit和atexit与on_exit
2015-10-01 Better types in C++11 - nullptr, enum classes (strongly typed enumerations) and cstdint