这才是C++泛型之美

C语言编程基础

共 829字,需浏览 2分钟

 · 2020-12-03

8cd10cb9de70d8f28e6619b5b9f4755e.webp

1058cbc0a753beb795ae95bcccfba0f7.webp前言

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

1058cbc0a753beb795ae95bcccfba0f7.webp初识可变模版参数

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

template <class... T> void f(T... args);

省略号的作用:

  • 声明一个参数包T... args,参数包中可包含0到n个模板参数;

  • 在模板定义的右边,可以将参数包展开成一个一个独立的参数。

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

  • 可变模版参数函数

  • 可变模版参数类

1058cbc0a753beb795ae95bcccfba0f7.webp可变模板参数函数1

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


#include #include using namespace std;template <class ...Type>void print(Type ...data) {  cout << sizeof...(data) << endl;}int main() {  print();  print(1);  print(1, "ILoveyou");  print(1, 2, 3.4, "IMissyou");  return 0;}

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

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

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

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

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

#include using namespace std;//递归终止函数void print(){    cout << "递归终止函数" << endl;}//展开函数template <class T,class ...Type>void print(T data,Type...exData){    cout << data << endl;    print(exData...);}int main(){    print(1, 2, 3, 4);    return 0;}

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

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

#include 
using namespace std;
//递归终止函数
template <typename Type>
Type sum(Type t)
{
   return t;
}
//展开函数
template <class T,class ...Type>
T sum(T a, Type ...b)
{
   return a + sum<T>(b...);
}
int main()
{
   cout << sum(1, 2, 3, 4) << endl;
   cout << sum(1, 2, 3) << endl;
   return 0;
}

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

3逗号表达式展开参数包

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

#include 
using namespace std;
//递归终止函数
template <class T>
void print(T data)
{
   cout << data << "\t";
}
template <class ...Type>
void print(Type ...exData)
{
   int array[] = { (print(exData),0)... };
}

int main()
{
   print(1, 2, 3);
   cout << endl;
   print("张三", 1, 3);
   return 0;
}

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

template<class Fclass... Args>void expand(const FfArgs&&...args{  initializer_list<int>{(f(std::forward< Args>(args)),0)...};}expand([](int i){cout<endl;}, 1,2,3);


1058cbc0a753beb795ae95bcccfba0f7.webp

可变模版参数类

1库中的可变模板参数类

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

template< class... Types >
class tuple;

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

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

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

tuple<> tp;

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

2继承方式展开参数包


//整型序列的定义template<int...>struct IndexSeq{};
//继承方式,开始展开参数包template<int N, int... Indexes>struct MakeIndexes : MakeIndexes1, N - 1, Indexes...> {};
// 模板特化,终止展开参数包的条件template<int... Indexes>struct MakeIndexes<0, Indexes...>{ typedef IndexSeq type;};
int main(){ using T = MakeIndexes<3>::type; cout <<typeid(T).name() << endl; return 0;}
1058cbc0a753beb795ae95bcccfba0f7.webp可变模板作用1可变参数模版消除重复代码

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

#include 
using namespace std;
template<typename T, typename...  Args>
T* Instance(Args... args)
{
   return new T(args...);
}
class A
{
public:
   A(int a) :a(a) {}
   A(int a, int b) :a(a) {}
   A(int a, int b,string c) :a(a) {}
   void print()
  {
       cout << a << endl;
  }
   int a;
};
class B
{
public:
   B(int a, int b) :a(a), b(b) {}
   void print()
  {
       cout << a << endl;
       cout << b << endl;
  }
   int a;
   int b;
};
int main()
{
   A* pa = Instance<A>(1);
   B* pb = Instance<B>(1, 2);
   pa->print();
   pb->print();
   pa = Instance<A>(100, 2);
   pa->print();
   pa = Instance<A>(100, 2,"Loveyo");
   pa->print();
   return 0;
}
2可变参数模版"万能函数"

万能函数类似C#中的委托功能,具体实现如下:

template <class T, class R, typename... Args>class  MyDelegate{public:    MyDelegate(T* t, R  (T::*f)(Args...) ):m_t(t),m_f(f) {}    R operator()(Args... args){        return (m_t->*m_f)(args ...);    }    //R operator()(Args&&... args)     //{            //return (m_t->*m_f)(std::forward(args) ...);    //}
private: T* m_t; R (T::*m_f)(Args...);};
template <class T, class R, typename... Args>MyDelegate CreateDelegate(T* t, R (T::*f)(Args...)){ return MyDelegate(t, f);}
struct A{ void Fun(int i){cout<endl;} void Fun1(int i, double j){cout<endl;}};
int main(){ A a; auto d = CreateDelegate(&a, &A::Fun); //创建委托 d(1); //调用委托,将输出1 auto d1 = CreateDelegate(&a, &A::Fun1); //创建委托 d1(1, 2.5); //调用委托,将输出3.5}


尾言

本栏到这里结束了,作业:自己谢谢可变参数模板。难度不大,重在重载。




浏览 30
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报