求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
要资料
 
追随技术信仰

随时听讲座
每天看新闻
 
 
C++并发编程(中文版)
前言
第1章 你好,C++的并发世界!
1.1 何谓并发
1.2 为什么使用并发?
1.3 C++中的并发和多线程
1.4 开始入门
1.5 本章总结
第2章 线程管理
2.1 线程管理的基础
2.2 向线程函数传递参数
2.3 转移线程所有权
2.4 运行时决定线程数量
2.5 识别线程
2.6 本章总结
第3章 线程间共享数据
3.1 共享数据带来的问题
3.2 使用互斥量保护共享数据
3.3 保护共享数据的替代设施
3.4 本章总结
第4章 同步并发操作
4.1 等待一个事件或其他条件
4.2 使用期望等待一次性事件
4.3 限定等待时间
4.4 使用同步操作简化代码
4.5 本章总结
第5章 C++内存模型和原子类型操作
5.1 内存模型基础
5.2 C++中的原子操作和原子类型
5.3 同步操作和强制排序
5.4 本章总结
第6章 基于锁的并发数据结构设计
6.1 为并发设计的意义何在?
6.2 基于锁的并发数据结构
6.3 基于锁设计更加复杂的数据结构
6.4 本章总结
第7章 无锁并发数据结构设计
7.1 定义和意义
7.2 无锁数据结构的例子
7.3 对于设计无锁数据结构的指导建议
7.4 本章总结
第8章 并发代码设计
8.1 线程间划分工作的技术
8.2 影响并发代码性能的因素
8.3 为多线程性能设计数据结构
8.4 设计并发代码的注意事项
8.5 在实践中设计并发代码
8.6 本章总结
第9章 高级线程管理
9.1 线程池
9.2 中断线程
9.3 本章总结
第10章 多线程程序的测试和调试
10.1 与并发相关的错误类型
10.2 定位并发错误的技术
10.3 本章总结
第11章 C++11语言特性简明参考(部分)
11.1 右值引用
11.2 删除函数
11.3 默认函数
11.4 常量表达式函数
11.5 Lambda函数
11.6 变参模板
11.7 自动推导变量类型
11.8 线程本地变量
 

 
目录
变参模板
作者:Anthony Williams  译者:陈晓伟
32 次浏览
5次  

A.6 变参模板

变参模板:就是可以使用不定数量的参数进行特化的模板。就像你接触到的变参函数一样,printf就接受可变参数。现在,就可以给你的模板指定不定数量的参数了。变参模板在整个C++线程库中都有使用,例如:std::thread的构造函数就是一个变参类模板。从使用者的角度看,仅知道模板可以接受无限个参数就够了,不过当要写这么一个模板或对其工作原理很感兴趣时,就需要了解一些细节。

和变参函数一样,变参部分可以在参数列表章使用省略号...代表,变参模板需要在参数列表中使用省略号:

  1. template<typename ... ParameterPack>
  2. class my_template
  3. {};

 

即使主模板不是变参模板,模板进行部分特化的类中,也可以使用可变参数模板。例如,std::packaged_task<>(见4.2.1节)的主模板就是一个简单的模板,这个简单的模板只有一个参数:

  1. template<typename FunctionType>
  2. class packaged_task;

不过,并不是所有地方都这样定义;对于部分特化模板来说,其就像是一个“占位符”:

  1. template<typename ReturnType,typename ... Args>
  2. class packaged_task<ReturnType(Args...)>;

部分特化的类就包含实际定义的类;在第4章,可以写一个std::packaged_task<int(std::string,double)>来声明一个以std::string和double作为参数的任务,当执行这个任务后结果会由std::future<int>进行保存。

声明展示了两个变参模板的附加特性。第一个比较简单:普通模板参数(例如ReturnType)和可变模板参数(Args)可以同时声明。第二个特性,展示了Args...特化类的模板参数列表中如何使用,为了展示实例化模板中的Args的组成类型。实际上,因为这是部分特化,所以其作为一种模式进行匹配;在列表中出现的类型(被Args捕获)都会进行实例化。参数包(parameter pack)调用可变参数Args,并且使用Args...作为包的扩展。

和可变参函数一样,变参部分可能什么都没有,也可能有很多类型项。例如,std::packaged_task<my_class()>中ReturnType参数就是my_class,并且Args参数包是空的,不过std::packaged_task<void(int,double,my_class&,std::string*)>中,ReturnType为void,并且Args列表中的类型就有:int, double, my_class&和std::string*。

A.6.1 扩展参数包

变参模板主要依靠包括扩展功能,因为不能限制有更多的类型添加到模板参数中。首先,列表中的参数类型使用到的时候,可以使用包扩展,比如:需要给其他模板提供类型参数。

  1. template<typename ... Params>
  2. struct dummy
  3. {
  4. std::tuple<Params...> data;
  5. };

成员变量data是一个std::tuple<>实例,包含所有指定类型,所以dummy的成员变量就为std::tuple<int, double, char>。

可以将包扩展和普通类型相结合:

  1. template<typename ... Params>
  2. struct dummy2
  3. {
  4. std::tuple<std::string,Params...> data;
  5. };

这次,元组中添加了额外的(第一个)成员类型std::string。其优雅指出在于,可以通过包扩展的方式创建一种模式,这种模式会在之后将每个元素拷贝到扩展之中,可以使用...来表示扩展模式的结束。

例如,创建使用参数包来创建元组中所有的元素,不如在元组中创建指针,或使用std::unique_ptr<>指针,指向对应元素:

  1. template<typename ... Params>
  2. struct dummy3
  3. {
  4. std::tuple<Params* ...> pointers;
  5. std::tuple<std::unique_ptr<Params> ...> unique_pointers;
  6. };

类型表达式会比较复杂,提供的参数包是在类型表达式中产生,并且表达式中使用...作为扩展。当参数包已经扩展 ,包中的每一项都会代替对应的类型表达式,在结果列表中产生相应的数据项。因此,当参数包Params包含int,int,char类型,那么std::tuple<std::pair<std::unique_ptr<Params>,double> ... >将扩展为std::tuple<std::pair<std::unique_ptr<int>,double>,std::pair<std::unique_ptr<int>,double>,std::pair<std::unique_ptr<char>, double> >。如果包扩展被当做模板参数列表使用,那么模板就不需要变长的参数了;如果不需要了,参数包就要对模板参数的要求进行准确的匹配:

  1. template<typename ... Types>
  2. struct dummy4
  3. {
  4. std::pair<Types...> data;
  5. };
  6. dummy4<int,char> a; // 1 ok,为std::pair<int, char>
  7. dummy4<int> b; // 2 错误,无第二个类型
  8. dummy4<int,int,int> c; // 3 错误,类型太多

 

可以使用包扩展的方式,对函数的参数进行声明:

  1. template<typename ... Args>
  2. void foo(Args ... args);

这将会创建一个新参数包args,其是一组函数参数,而非一组类型,并且这里...也能像之前一样进行扩展。例如,可以在std::thread的构造函数中使用,使用右值引用的方式获取函数所有的参数(见A.1节):

  1. template<typename CallableType,typename ... Args>
  2. thread::thread(CallableType&& func,Args&& ... args);

函数参数包也可以用来调用其他函数,将制定包扩展成参数列表,匹配调用的函数。如同类型扩展一样,也可以使用某种模式对参数列表进行扩展。

例如,使用std::forward()以右值引用的方式来保存提供给函数的参数:

  1. template<typename ... ArgTypes>
  2. void bar(ArgTypes&& ... args)
  3. {
  4. foo(std::forward<ArgTypes>(args)...);
  5. }

注意一下这个例子,包扩展包括对类型包ArgTypes和函数参数包args的扩展,并且省略了其余的表达式。

当这样调用bar函数:

  1. int i;
  2. bar(i,3.141,std::string("hello "));

将会扩展为

  1. template<>
  2. void bar<int&,double,std::string>(
  3. int& args_1,
  4. double&& args_2,
  5. std::string&& args_3)
  6. {
  7. foo(std::forward<int&>(args_1),
  8. std::forward<double>(args_2),
  9. std::forward<std::string>(args_3));
  10. }

这样就将第一个参数以左值引用的形式,正确的传递给了foo函数,其他两个函数都是以右值引用的方式传入的。

最后一件事,参数包中使用sizeof...操作可以获取类型参数类型的大小,sizeof...(p)就是p参数包中所包含元素的个数。不管是类型参数包或函数参数包,结果都是一样的。这可能是唯一一次在使用参数包的时候,没有加省略号;这里的省略号是作为sizeof...操作的一部分,所以不算是用到省略号。

下面的函数会返回参数的数量:

  1. template<typename ... Args>
  2. unsigned count_args(Args ... args)
  3. {
  4. return sizeof... (Args);
  5. }

就像普通的sizeof操作一样,sizeof...的结果为常量表达式,所以其可以用来指定定义数组长度,等等。


您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码: 验证码,看不清楚?请点击刷新验证码 必填



32 次浏览
5次