C++11 新特新中的变长模板给我们写代码带来了很大的灵活性,方便了不少。现在我们终于可以写出调用不同参数的函数的代码了。变长模板的使用关键在于包的展开,在 C++11 的书中有介绍包展开的位置位置有以下几种:
- 表达式
- 初始化列表
- 基类描述列表
- 类成员初始化列表
- 模板参数列表
- 通用属性列表
- lamda 表达式列表
由于本人对以上几种也不是完全理解,只用过几种情况。下面先看两个小例子
例子1: 计算不定个数的自然数乘积(取自 C+11 的书中)
template<long... nums> struct MultiplyNums;
template<long First, long... Last>
struct MultiplyNums<First, Last...>
{
static const long value = First * MultiplyNums<Last...>::value;
};
template<>
struct MultiplyNums<> { static const long value = 1; };
这样通过在表达式中递归展开包 "Last..." ,递归的截止情况为最后定义的 MultiplyNums<> 。例如计算 1*2*3*4*5 就可以写作
int value = MultiplyNums<1, 2, 3, 4, 5>::value;
例子2: 不同参数的函数调用
void TestFunction1()
{
}
void TestFunction2(int value)
{
int a = value;
}
template <class T>
T& expandParams(T& t)
{
return t;
}
template <class Function, class ... Args>
void CallTestFunction(Function pFunction, Args ... args)
{
((*pFunction))(expandParams(args) ...);
}
// 调用
CallTestFunction(&TestFunction2, 2);
这个帖子主要的目的是介绍变长模板如何更简洁更方便的调用函数,上面的例子2只是一个很简单的例子,而且适用场合比较有限。我在工作中经常会使用到在另外一个地方会调用当前类的成员方法,而且是支持不同类的不同的成员方法的调用,上面的例子就无法满足了。 我们先来看看,C++98 时如果要实现这样的需求需要如何实现:
首先定义一个接受不同类不同函数的模板类:
#pragma once
template< typename T1 >
class Params
{
public:
Params() {}
~Params() {}
public:
Params(const T1 &p1)
:t1(p1)
{
}
Params(const Params<T1> &other)
{
*this = other;
}
Params<T1>& operator=(const Params<T1> &other)
{
if (this != &other)
{
t1 = other.t1;
}
return *this;
}
public:
T1& v1() { return t1; }
protected:
T1 t1;
};
class IFunctionCall
{
public:
virtual ~IFunctionCall() {}
public:
virtual void Execute() = 0;
};
template<class Fun, class Obj>
class CFunctionCall0 : public IFunctionCall
{
public:
CFunctionCall0(Fun fun, Obj obj)
: m_pFun(fun)
, m_pObj(obj)
{
}
virtual ~CFunctionCall0() {}
public:
virtual void Execute() { ((*m_pObj).*m_pFun)(); };
private:
Fun m_pFun;
Obj m_pObj;
};
template<class Fun, class Obj, class T1>
class CFunctionCall1 : public IFunctionCall
{
public:
CFunctionCall1(Fun fun, Obj obj, T1 t1)
: m_pFun(fun)
, m_pObj(obj)
, m_t1(t1)
{
}
virtual ~CFunctionCall1() {}
public:
virtual void Execute() { ((*m_pObj).*m_pFun)(m_t1.v1()); };
private:
Fun m_pFun;
Obj m_pObj;
Params<T1> m_t1;
};
上面只是简单写的例子,写的并不完善,大家理解其含义即可。上面列出了不带参数和带一个参数的,平时我们在使用过程中一般要定义到 9 个参数的,可想而知全部写出来要有多少代码。 然后定义一个创建模板的模板方法,这样我们就可以通过这个模板方法将 IFunctionCall* 传递给一个接受这个类型为参数的函数了, 之后在那个函数中可以在适当的时机调用函数。
template<class Fun, class Obj, class T1>
IFunctionCall* BindFunction(Fun pFun, Obj pObj, T1 p1)
{
IFunctionCall* pCall = new CFunctionCall1< Fun, Obj, T1>(pFun, pObj, p1);
return pCall;
}
实际使用过程中,我们还得定义其他不同参数个数的 BindFunction 才行。
一个调用的例子如下, 其中 MyTest::TestFunction1 可以是一个以一个 int 类型为参数的函数
IFunctionCall* pFunCall = BindFunction(&MyTest::TestFunction1, &test, 2); pFunCall->Execute();
而有了 C++11 的变长模板之后,我们终于可以摆脱上面的繁杂的代码了。做法如下:
- 将 CFunctionCall1 / CFunctionCall2 ... CFunctionCall9 这样的类定义为变长模板类
- 将接收的参数用 std::tuple 来接收 (tuple 是 C++11 中内置的变长模板类, 我在尝试写变长模板类的时候曾经尝试不用 tuple,最后有些困难还是放弃了)。使用 tuple 的话我们就不必在参数传入的时候进行展开了,直接赋值给 tuple 的成员变量即可
- 将 BindFunction 定义为变长模板函数,当然了,在这个函数中只是负责将参数包传递给变长模板类
- 改写 Excute() 方法,之前的方法参数固定。而这里我们需要从 tuple 成员中取得对应的参数。这里我当初也碰到了一些问题,最后找到了 C++14 中的 std::make_index_sequence 方法,可以通过将 tuple 的 size 传入,得到一个 0,1,2,3,4... 这样的自然数序列, 然后通过 get<index>(tuple) 逐个传入 index 取得 tuple 中实际存的变量。
相信聪明的你们已经大概明白了如何通过较少的代码实现变长模板类来调用函数了。 希望大家尝试写一下,当然也可以参考我的代码:我的例子。
上面的方法只是我读过 C++11 变长模板后自己想出来的一个方法,可能不是个好方法,我认为还是应该有更好的写法的,希望有其他见解的朋友可以留言,多多赐教