文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

可能学了假的编程?C++新标准难点解析之可变模板参数

2024-12-03 11:53

关注

C++的新特性--可变模版参数(variadic templates)是C++新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以它也是C++中最难理解和掌握的特性之一。虽然掌握可变模版参数有一定难度,但是它却是C++11中最有意思的一个特性,本文希望带领读者由浅入深地认识和掌握这一特性,同时也会通过一些实例来展示可变参数模版的一些用法。

[[380966]]

变模版参数的展开

可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号“...”。比如我们常常这样声明一个可变模版参数:template

  1. template  
  2. void f(T... args); 

 省略号的作用:

声明一个参数包T... args,这个参数包中可以包含0到任意个模板参数; 2.在模板定义的右边,可以将参数包展开成一个一个独立的参数。

省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。

可变模板参数分类:

可变模版参数函数

可变模版参数类

打印可变模版参数函数的参数个数

  1. #include  
  2. #include  
  3. using namespace std; 
  4. template  
  5. void print(Type ...data)  
  6.     cout << sizeof...(data) << endl; 
  7. int main()  
  8.     print(); 
  9.     print(1); 
  10.     print(1, "ILoveyou"); 
  11.     print(1, 2, 3.4, "IMissyou"); 
  12.     return 0; 

 上面的例子中,print()没有传入参数,所以参数包为空,输出的size为0,后面两次调用分别传入两个和三个参数,故输出的size分别为2和3。由于可变模版参数的类型和个数是不固定的,所以我们可以传任意类型和个数的参数给函数print。这个例子只是简单的将可变模版参数的个数打印出来,如果我们需要将参数包中的每个参数打印出来的话就需要通过一些方法了。

展开可变模版参数函数的方法一般有两种:

通过递归函数来展开参数包。

逗号表达式来展开参数包。

递归方式展开参数包

通过递归函数展开参数包,需要提供一个参数包展开的函数和一个递归终止函数,递归终止函数正是用来终止递归的,如下面的例子:

  1. #include  
  2. using namespace std; 
  3. //递归终止函数 
  4. void print() 
  5.     cout << "递归终止函数" << endl; 
  6. //展开函数 
  7. template  
  8. void print(T data,Type...exData) 
  9.     cout << data << endl; 
  10.     print(exData...); 
  11. int main() 
  12.     print(1, 2, 3, 4); 
  13.     return 0; 

 上例会输出每一个参数,直到为空时输出"递归终止函数"。展开参数包的函数有两个,一个是递归函数,另外一个是递归终止函数,参数包exData...在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用非模板函数print终止递归过程。当然上述终止函数也可以写成带参数函数模板:

  1. template  
  2. void print(T data) 
  3.  cout<

 接下来用模板函数作为终止函数写一个不限参求和函数,具体实现代码如下:

  1. #include  
  2. using namespace std; 
  3. //递归终止函数 
  4. template  
  5. Type sum(Type t)  
  6.     return t; 
  7. //展开函数 
  8. template  
  9. sum(T a, Type ...b)  
  10.     return a + sum(b...); 
  11. int main() 
  12.     cout << sum(1, 2, 3, 4) << endl; 
  13.     cout << sum(1, 2, 3) << endl; 
  14.     return 0; 

 sum在展开参数包的过程中将各个参数相加求和,参数的展开方式和前面的打印参数包的方式是一样的。

逗号表达式展开参数包

递归函数展开参数包是一种标准做法,也比较好理解,但也有一个缺点,就是必须要一个重载的递归终止函数,即必须要有一个同名的终止函数来终止递归,这样可能会感觉稍有不便。有没有一种更简单的方式呢?其实还有一种方法可以不通过递归方式来展开参数包,这种方式需要借助逗号表达式和初始化列表。比如前面打印函数可以改成这样:

  1. #include  
  2. using namespace std; 
  3. //递归终止函数 
  4. template  
  5. void print(T data)  
  6.     cout << data << "\t"
  7. template  
  8. void print(Type ...exData)  
  9.     int array[] = { (print(exData),0)... }; 
  10.  
  11. int main() 
  12.     print(1, 2, 3); 
  13.     cout << endl; 
  14.     print("张三", 1, 3); 
  15.     return 0; 

 这个数组的目的纯粹是为了在数组构造的过程展开参数包。我们可以把上面的例子再进一步改进一下,将函数作为参数,就可以支持lambda表达式了,从而可以少写一个递归终止函数了,具体代码如下:

C++中新特性之:initializer_list详解

C++11提供的新类型,定义在头文件中。

  1. template< class T > 
  2. class initializer_list; 

 下面稍微介绍一下initializer_list

一个initializer_list当出现在以下两种情况的被自动构造:

  1. 当初始化的时候使用的是大括号初始化,被自动构造。包括函数调用时和赋值
  2. 当涉及到for(initializer: list),list被自动构造成initializer_list对象

也就是说initializer_list对象只能用大括号{}初始化。拷贝一个initializer_list对象并不会拷贝里面的元素。其实只是引用而已。而且里面的元素全部都是const的。下面一个例子可以帮助我们更好地理解如何使用initializer_list:

  1. #include  
  2. #include  
  3. #include  
  4.  
  5. using namespace std; 
  6.  
  7. template  
  8. struct S  
  9.     vector v; 
  10.     S(initializer_list l) : v(l) { 
  11.         cout << "constructed with a " << l.size() << "-elements lists" << endl; 
  12.     } 
  13.     void append(std::initializer_list l) { 
  14.         v.insert(v.end(), l.begin(), l.end()); 
  15.     } 
  16.  
  17.     pair c_arr() const { 
  18.         return { &v[0], v.size() }; 
  19.     } 
  20. }; 
  21. template  
  22. void templated_fn(T arg) { 
  23.     for (auto a : arg) 
  24.         cout << a << " "
  25.     cout << endl; 
  26.  
  27. int main()  
  28.     S<int> s = { 1, 2, 3, 4, 5 };                 
  29.     s.append({ 6, 7 , 8 });         
  30.     for (auto n : s.v) 
  31.         cout << ' ' << n; 
  32.     cout << endl; 
  33.     for (auto x : { -1, -2, 03 })   
  34.         cout << x << " "
  35.     cout << endl; 
  36.     auto al = { 10, 11, 12 };  
  37.     templated_fnint> >({ 7, 8, 9 });  
  38.     templated_fnint>>({ 3, 5, 7 });        
  39.  
  40.     return 0; 

 可变模版参数类

std::tuple就是一个可变模板类

  1. template< class... Types > 
  2. class tuple; 

 这个可变参数模板类可以携带任意类型任意个数的模板参数:

  1. tuple<int> tp1 = std::make_tuple(1); 
  2. tuple<intdouble> tp2 = std::make_tuple(1, 2.5); 
  3. tuple<intdouble, string> tp3 = std::make_tuple(1, 2.5, “”); 

 可变参数模板的模板参数个数可以为0个,所以下面的定义也是也是合法的:

  1. tuple<> tp; 

可变参数模板类的参数包展开的方式和可变参数模板函数的展开方式不同,可变参数模板类的参数包展开需要通过模板特化和继承方式去展开,展开方式比可变参数模板函数要复杂。

模版偏特化和递归方式来展开参数包

基本的可变参数模板类

  1. //前向声明 
  2. template 
  3. struct Sum
  4.  
  5. //基本定义 
  6. templateFirst, typename... Rest> 
  7. struct Sum<First, Rest...> 
  8.     enum { value = Sum<First>::value + Sum::value }; 
  9. }; 
  10.  
  11. //递归终止 
  12. templateLast
  13. struct Sum<Last
  14.     enum { value = sizeof (Last) }; 
  15. }; 
  16. int main() 
  17.     cout << Sum<intdouble, short>::value << endl; 
  18.     return 0; 

 继承方式展开参数包

  1. //整型序列的定义 
  2. template<int...> 
  3. struct IndexSeq{}; 
  4.  
  5. //继承方式,开始展开参数包 
  6. template<int N, int... Indexes> 
  7. struct MakeIndexes : MakeIndexes {}; 
  8.  
  9. // 模板特化,终止展开参数包的条件 
  10. template<int... Indexes> 
  11. struct MakeIndexes<0, Indexes...> 
  12.     typedef IndexSeq type; 
  13. }; 
  14.  
  15. int main() 
  16.     using T = MakeIndexes<3>::type; 
  17.     cout <name() << endl; 
  18.     return 0; 

 可变参数模版消除重复代码

C++11之前如果要写一个泛化的工厂函数,这个工厂函数能接受任意类型的入参,并且参数个数要能满足大部分的应用需求的话,我们不得不定义很多重复的模版定义,比如下面的代码:

  1. #include  
  2. using namespace std; 
  3. template 
  4. T* Instance(Args... args) 
  5.     return new T(args...); 
  6. class A 
  7. public
  8.     A(int a) :a(a) {} 
  9.     A(int a, int b) :a(a) {} 
  10.     A(int a, int b,string c) :a(a) {} 
  11.     void print()  
  12.     { 
  13.         cout << a << endl; 
  14.     } 
  15.     int a; 
  16. }; 
  17. class B  
  18. public
  19.     B(int a, int b) :a(a), b(b) {} 
  20.     void print() 
  21.     { 
  22.         cout << a << endl; 
  23.         cout << b << endl; 
  24.     } 
  25.     int a; 
  26.     int b; 
  27. }; 
  28. int main() 
  29.     A* pa = Instance(1); 
  30.     B* pb = Instance(1, 2); 
  31.     pa->print(); 
  32.     pb->print(); 
  33.     pa = Instance(100, 2); 
  34.     pa->print(); 
  35.     pa = Instance(100, 2,"Loveyo"); 
  36.     pa->print(); 
  37.     return 0; 

 

 万能函数

  1. template  
  2. class  MyDelegate 
  3. public
  4.     MyDelegate(T* t, R  (T::*f)(Args...) ):m_t(t),m_f(f) {} 
  5.     R operator()(Args... args) 
  6.     { 
  7.         return (m_t->*m_f)(args ...); 
  8.     } 
  9.     //R operator()(Args&&... args)  
  10.     //{ 
  11.             //return (m_t->*m_f)(std::forward(args) ...); 
  12.     //} 
  13.  
  14. private: 
  15.     T* m_t; 
  16.     R  (T::*m_f)(Args...); 
  17. };    
  18.  
  19. template  
  20. MyDelegate CreateDelegate(T* t, R (T::*f)(Args...)) 
  21.     return MyDelegate(t, f); 
  22.  
  23. struct A 
  24.     void Fun(int i){cout<
  25.     void Fun1(int i, double j){cout<
  26. }; 
  27.  
  28. int main() 
  29.     A a; 
  30.     auto d = CreateDelegate(&a, &A::Fun); //创建委托 
  31.     d(1); //调用委托,将输出1 
  32.     auto d1 = CreateDelegate(&a, &A::Fun1); //创建委托 
  33.     d1(1, 2.5); //调用委托,将输出3.5 

 【编辑推荐】

  1. CDN:什么是边缘CDN和虚拟CDN(vCDN)?
  2. 终于有人将数据中台讲清楚了,原来根本不算啥
  3. 4种速度最慢的动态编程语言,你一定用过
  4. 勒索软件攻击将在2021年爆发
  5. 为什么有些高级开发人员不喜欢Python

 

来源:今日头条内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯