C++中的变长参数深入理解

2020-01-06 15:53:16刘景俊


template<class T>
T *MemNew(size_t count)
{
 T *p = (T*)MemPool<T, count>::Allocate();
 if (p != NULL)
 {
  if (!std::is_pod<T>::value)
  {
   for (size_t i = 0; i < count; ++i)
   {
    new (&p[i]) T();
   }
  }
 }
 return p;
}

template<class T>
T *MemDelete(T *p, size_t count)
{
 if (p != NULL)
 {
  if (!std::is_pod<T>::value)
  {
   for (size_t i = 0; i < count; ++i)
   {
    p[i].~T();
   }
  }
  MemPool<T, count>::DeAllocate(p);
 }
}

上述实现中,使用placement new对申请的内存进行构造,使用了默认构造函数,当申请内存的类型不具备默认构造函数时,placement new将报错。对于pod类型,可以省去调用构造函数的过程。

引入C++11变长模板参数后MemNew修改为如下


template<class T, class... Args>
T *MemNew(size_t count, Args&&... args)
{
 T *p = (T*)MemPool<T, count>::Allocate();
 if (p != NULL)
 {
  if (!std::is_pod<T>::value)
  {
   for (size_t i = 0; i < count; ++i)
   {
    new (&p[i]) T(std::forward<Args>(args)...);
   }
  }
 }
 return p;
}

以上函数定义包含了多个特性,后面我将一一解释,其中class... Args 表示变长参数模板,函数参数中Args&& 为右值引用。std::forward<Args> 实现参数的完美转发。这样,无论传入的类型具有什么样的构造函数,都能够完美执行

C++11中引入了变长参数模板的概念,来解决参数个数不确定的模板。


template<class... T> class Test {};
Test<> test0;
Test<int> test1;
Test<int,int> test2;
Test<int,int,long> test3;

template<class... T> void test(T... args);
test();
test<int>(0);
test<int,int,long>(0,0,0L);

以上分别是使用变长参数类模板和变长参数函数模板的例子。

2.1变长参数函数模板

T... args 为形参包,其中args是模式,形参包中可以有0到任意多个参数。调用函数时,可以传任意多个实参。对于函数定义来说,该如何使用参数包呢?在上文的MemNew中,我们使用std::forward依次将参数包传入构造函数,并不关注每个参数具体是什么。如果需要,我们可以用sizeof...(args)操作获取参数个数,也可以把参数包展开,对每个参数做更多的事。展开的方法有两种,递归函数,逗号表达式。

递归函数方式展开,模板推导的时候,一层层递归展开,最后到没有参数时用定义的一般函数终止。


void test()
{
}

template<class T, class... Args> 
void test(T first, Args... args)
{
 std::cout << typeid(T).name() << " " << first << std::endl;
 test(args...);
}

test<int, int, long>(0, 0, 0L);

output:
int 0
int 0
long 0