這纔是C++泛型之美

前言

上一章節主要是詳細介紹了C++泛型編程基礎,不清楚的能夠回顧一下哦。本章節主要針對於C++STL(標準模板類庫)作個詳細介紹。C++的新特性--可變模版參數(variadic templates)是C++新增的最強大的特性之一,它對參數進行了高度泛化,它能表示0到任意個數、任意類型的參數。相比C++98/03,類模版和函數模版中只能含固定數量的模版參數,可變模版參數無疑是一個巨大的改進。然而因爲可變模版參數比較抽象,使用起來須要必定的技巧,因此它也是C++中最難理解和掌握的特性之一。雖然掌握可變模版參數有必定難度,可是它倒是C++中最有意思的一個特性,本文但願帶領讀者由淺入深的認識和掌握這一特性,同時也會經過一些實例來展現可變參數模版的一些用法。html

初識可變模版參數

可變參數模板和普通模板的語義是同樣的,只是寫法上稍有區別,聲明可變參數模板時須要在typenameclass後面帶上省略號「...」。好比咱們經常這樣聲明一個可變模版參數:template<typename...>或者template<class...>,一個典型的可變模版參數的定義是這樣的:ios

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

省略號的做用:c++

  • 聲明一個參數包T... args,參數包中可包含0到n個模板參數;編程

  • 在模板定義的右邊,能夠將參數包展開成一個一個獨立的參數。數組

省略號的參數稱爲參數包,它裏面包含了0NN>=0)個模版參數。咱們沒法直接獲取參數包args的每一個參數的,只能經過展開參數包的方式來獲取參數包中的每一個參數,這是使用可變模版參數的一個主要特色,也是最大的難點,即如何展開可變模版參數。可變模板參數分類:微信

  • 可變模版參數函數app

  • 可變模版參數類編輯器

可變模板參數函數

1

打印可變模版參數函數的參數個數


#include <iostream>#include <string>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。這個例子只是簡單的將可變模版參數的個數打印出來,若是咱們須要將參數包中的每一個參數打印出來的話就須要經過一些方法了。展開可變模版參數函數的方法通常有兩種:ide

1.經過遞歸函數來展開參數包。函數

2.逗號表達式來展開參數包。

2
經過遞歸函數來展開參數包

經過遞歸函數展開參數包,須要提供一個參數包展開的函數和一個遞歸終止函數,遞歸終止函數正是用來終止遞歸的,以下面的例子:

#include <iostream>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 <iostream>
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 <iostream>
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<<i<<endl;}, 1,2,3);


可變模版參數類

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 : MakeIndexes<N - 1, N - 1, Indexes...> {};
// 模板特化,終止展開參數包的條件template<int... Indexes>struct MakeIndexes<0, Indexes...>{ typedef IndexSeq<Indexes...> type;};
int main(){ using T = MakeIndexes<3>::type; cout <<typeid(T).name() << endl; return 0;}

可變模板做用

1

變參數模版消除重複代碼


C++11以前若是要寫一個泛化的工廠函數,這個工廠函數能接受任意類型的入參,而且參數個數要能知足大部分的應用需求的話,咱們不得不定義不少重複的模版定義,好比下面的代碼:

#include <iostream>
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>(args) ...); //}
private: T* m_t; R (T::*m_f)(Args...);};
template <class T, class R, typename... Args>MyDelegate<T, R, Args...> CreateDelegate(T* t, R (T::*f)(Args...)){ return MyDelegate<T, R, Args...>(t, f);}
struct A{ void Fun(int i){cout<<i<<endl;} void Fun1(int i, double j){cout<<i+j<<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}


尾言

本欄到這裏結束了,做業:本身謝謝可變參數模板。難度不大,重在重載。




本文分享自微信公衆號 - C語言編程基礎(goodStudyCode)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索