C++11 理解 (十七) 之 变长参数模板

在 C++11 之前, 不论是类模板或是函数模板,都只能按其被声明时所指定的样子,接受一组固定数目的模板参数 ; C++11 加入新的表示法,允许任意个数、任意类别的模板参数,不必在定义时将参数的个数固定。

template<typename... Values> class tuple;

模板类 tuple 的对象,能接受不限个数的 typename 作为它的模板形参:

class tuple<int, std::vector<int>, std::map<std::string, std::vector<int>>> someInstanceName;

实参的个数也可以是 0,所以 class tuple<> someInstanceName 这样的定义也是可以的。

若不希望产生实参个数为 0 的变长参数模板,则可以采用以下的定义:

template<typename First, typename... Rest> class tuple;

变长参数模板也能运用到模板函数上。 传统 C 中的 printf 函数,虽然也能达成不定个数的形参的调用,但其并非类别安全。 以下的样例中,C++11 除了能定义类别安全的变长参数函数外,还能让类似 printf 的函数能自然地处理非自带类别的对象。 除了在模板参数中能使用...表示不定长模板参数外,函数参数也使用同样的表示法代表不定长参数。

template<typename... Params> void printf(const std::string &strFormat, Params... parameters);

其中,Params 与 parameters 分别代表模板与函数的变长参数集合, 称之为参数包 (parameter pack)。参数包必须要和运算符"..."搭配使用,避免语法上的歧义。

变长参数模板中,变长参数包无法如同一般参数在类或函数中使用; 因此典型的手法是以递归的方法取出可用参数,参看以下的 C++11 printf 样例:

void printf(const char *s)
{
  while (*s)
  {
    if (*s == '%' && *(++s) != '%')
      throw std::runtime_error("invalid format string: missing arguments");
    std::cout << *s++;
  }
}
 
template<typename T, typename... Args>
void printf(const char* s, T value, Args... args)
{
  while (*s)
  {
    if (*s == '%' && *(++s) != '%')
    {
      std::cout << value;
      printf(*s ? ++s : s, args...); // 即便当 *s == 0 也会产生调用,以检测更多的类型参数。
      return;
    }
    std::cout << *s++;
  }
  throw std::logic_error("extra arguments provided to printf");
}

printf 会不断地递归调用自身:函数参数包 args... 在调用时, 会被模板类别匹配分离为 T value和 Args... args。 直到 args... 变为空参数,则会与简单的printf(const char *s) 形成匹配,退出递归。

另一个例子为计算模板参数的个数,这里使用相似的技巧展开模板参数包 Args...

template<>
struct count<> {
    static const int value = 0;
};
 
template<typename T, typename... Args>
struct count<T, Args...> { 
    static const int value = 1 + count<Args...>::value;
};

虽然没有一个简洁的机制能够对变长参数模板中的值进行迭代,但使用运算符"..."还能在代码各处对参数包施以更复杂的展开操作。举例来说,一个模板类的定义:

template <typename... BaseClasses> class ClassName : public BaseClasses...
{
public:
 
   ClassName (BaseClasses&&... baseClasses) : BaseClasses(baseClasses)... {}
}

BaseClasses... 会被展开成类型 ClassName 的基底类; ClassName 的构造函数需要所有基类的左值引用,而每一个基类都是以传入的参数做初始化 (BaseClasses(baseClasses)...)。

在函数模板中,变长参数可以和左值引用搭配,达成形参的完美转送 (perfect forwarding):

template<typename TypeToConstruct> struct SharedPtrAllocator
{
  template<typename... Args> std::shared_ptr<TypeToConstruct> ConstructWithSharedPtr(Args&&... params)
  {
    return tr1::shared_ptr<TypeToConstruct>(new TypeToConstruct(std::forward<Args>(params)...));
  }
}

参数包 parms 可展开为 TypeToConstruct 构造函数的形参。 表达式std::forward<Args>(params) 可将形参的类别信息保留(利用右值引用),传入构造函数。 而运算符"..."则能将前述的表达式套用到每一个参数包中的参数。这种工厂函数(factory function)的手法, 使用 std::shared_ptr 管理配置对象的内存,避免了不当使用所产生的内存泄漏(memory leaks)。

此外,变长参数的数量可以藉以下的语法得知:

template<typename ...Args> struct SomeStruct
{
  static const int size = sizeof...(Args);
}

SomeStruct<Type1, Type2>::size 是 2,而 SomeStruct<>::size 会是 0。 (sizeof...(Args) 的结果是编译期常数。)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值