竞博体育 > 前端 > 竞博体育官方版下载  元函数是模板元编程中用于操作处理元数据的,在泛型编程中

竞博体育官方版下载  元函数是模板元编程中用于操作处理元数据的,在泛型编程中

<转自:〉

 

C++11模板元编制程序,11模板编制程序

C++太复杂了。小编估计你早晚是在Computer荧屏前不住地方头。其实自个儿也在大力的首肯。没有错,C++着实复杂,差非常的少是从那之后最复杂的语言。于是,便发出了叁个测算:C++难学难用。
那句话也十分精确,但不要所一时候都如此。C++的上学和利用能够分成若干个范畴,那些层面由简到繁,由易到难,分别切合分裂等级次序的学习者和使用者。
先让大家来拜望C++究竟有多复杂。作者差不离列了个清单,包涵C++的机要语言特色。基本的局地风味,象变量、条件、接受等等,都掠去了,主要汇聚在呈现C++复杂性的地点:

1、基本概念

1.概述

  模版元编制程序(template metaprogram)是C++中最复杂也是威力最苍劲的编制程序范式,它是一种能够创建和决定程序的次序。模版元编制程序完全分裂于普通的运维期程序,它异常特别,因为模版元程序的实施完全都以在编写翻译期,並且模版元程序决定的数量无法是运作时变量,只好是编写翻译期常量,不可改良,别的它使用的语法成分也是一对第一批简化汉字单,不可能利用运行期的部分语法,举个例子if-else,for等说话都不可能用。因而,模版元编制程序须求过多技能,平时须求类型重定义、枚举常量、世襲、模板偏特化等方法来协作,由此编写模版元编制程序相比复杂也相比困难。

  今后C++11骤增了一部分模板元相关的特征,不仅能够让大家编辑模版元程序变得更便于,还特别加强了泛型编制程序的本事,举例type_traits让我们无需再重复发明轮子了,给大家提供了汪洋方便的元函数,还提供了可变模板参数和tuple,让模版元编制程序“锦上添花”。本文将向读者浮现C++11中模版元编程常用的技巧和现实性使用。

  1. 指南针。包括变量指针、函数指针等。能够统计。
  2. 援用。包涵变量的引用、函数的引用等。
  3. 随意函数。
  4. 类。包含具体类和抽象类。
  5. 重载。包含函数重载和操作符重载。
  6. 成员数量。包涵static和非static的。
  7. 成员函数。富含static和非static的。
  8. 虚函数。包蕴对虚函数的override。
  9. 接轨。富含多三回九转、虚世襲等。
  10. 多态。包罗动多态和静多态。
  11. 类型转换。蕴涵隐式的和显式的,以致劣迹斑斑的勒迫调换。
  12. 模板。包蕴类模板和函数模板。
  13. 模板特化。富含完全特化和有个别特化。
  14. 非类型模板参数。
  15. 模板-模板参数。
    本身相比较了C#的特点项目清单。C++比C#多的表征首若是:1、2、3、9的多世袭、10的静多态、13、14、15。
    而C#比C++多的特色主要有(这里没有杜撰C++/CLI,C++/CLI补充了专门的学业C++):
  16. for   each关键字。
  17. as、is关键字。
  18. seal关键字。
  19. abstract关键字。
  20. override/new关键字。
  21. using关键字的有个别补偿用法。
  22. implicit/explicit关键字。(C++有explicit关键字,但只用于结构函数)
  23. interface关键字。
  24. property。
  25. delegate。
    作者们来解析一下。C++比C#所多的风味聚集在编制程序手艺方面,特别是编制程序方式,如由模板带给泛型编制程序和元编制程序机能。在OOP方面,C++走得更远,更深透,首要表现在多三番一回和操作符重载方面。在理念的编制程序技巧上,C++区分了对象和对象的援引(指针和援引)。而C#装有援引对象所持的都以引用。最后,C++具有自由函数。
    反过来再看C#,C#所多的都是些关键字。这几个首要字都聚集在OOP方面,况兼都以以使编制程序技能易于精通为指标的。很三人(蕴涵自己在内)都期望C++也享有C#的这个重大字(最少部分)。但与平常人的敞亮分歧,C++规范委员会事实上是被编写翻译器开辟商所把持着,他们对引加入关贸总协定组织键字尤其捕风捉影。未有艺术,现实总是有不满的。
    从那些地点能够见到,C++在编制程序机制上远远多于C#(Java的体制依旧更加少)。对于新入行的人来说,一口气吞下这几个剧情,足以把她们撑死。相反,C#充实的重大字有利于初读书人通晓代码的含义。那几个正是C#和Java比C++易于就学(易于驾驭)的真的原因。
    然则,必必要重申的是,C#和Java中容命理术数习和精晓的是代码,并不是这个代码背后的技能原理和背景。
    本身看出过的绝大大多C#代码都充斥了再次代码和大度switch操作分派。如果这么些程序猿充足利用C#和Java的OOP机制,那一个严重的代码冗余能够裁撤四分之二。(要是C#和Java具有C++那样的泛型编制程序技术,则另二分一也得以去掉。)这么些程序猿都以在尚未充裕知晓语言机制和OOP技巧的情事下编写制定软件,劳民伤财。
    这种景况在C++也会有产生,但针锋绝对一些些。那大概是因为C++丰裕复杂,使得学习者发生了“不到头精通C++就学不会C++,就用持续C++”的主见。这种主张有利有弊,利在于促使学习者丰硕驾驭语言和言语背后的本事,而弊在于它吓跑了累累人。实际上,大家说话就能够看出,C++能够同C#和Java同样,能够在不明白此中规律的场地下,仅仅依据既定法规编制程序。当然大家不愿意那样,那是不好的做法。但由于今后产业界
    的急性心态,大家也就入竟问禁吧。
    专一了!上边那句话是最器重的,最珍视的,也是被长时间忽略的:C++之所以复杂,是为了选取起来更简约。听不知晓!前后厌恶!胡说!别急,且听笔者逐步道来。
    (限于篇幅,小编那边只交给最后一有些案例代码,完整的案例在本人的blog里:State of Qatar
    有四个容器,c1、c2、c3。容器的花色和要素的档案的次序都茫茫然。须要写三个算法框架,把c1里的因素同c2里的因素进行某种运算,结果放到c3里。
    是因为容器类型未知,必须选取全数容器公共的接口。所以,笔者写下了之类的C#代码:
    public   delegate   void   alg <T1,   T2,   R> (T1   v1,   T2   v2,   R   r);
    public   static   void   Caculate_Contain <C1,   T1,   C2,   T2,   C3,   T3>
    (C1   c1,   C2   c2,   C3   c3,   alg <T1,   T2,   T3>   a   )
    where   C1:   IEnumerable <T1>
    where   C2   :   IEnumerable <T2>
    where   C3   :   IEnumerable <T3>
    {
    IEnumerator <T1>   eai1   =   c1.GetEnumerator();
    IEnumerator <T2>   eai2   =   c2.GetEnumerator();
    IEnumerator <T3>   eai3   =   c3.GetEnumerator();

所谓泛型编制程序:以单独于其余特定项指标主意编写代码。使用泛型程序时,大家供给提供具体程序实例所操作的品种或值。标准库的器皿、迭代器和算法都以泛型编制程序的例子。每一种容器如:vector卡塔尔国都有单纯的定义,但能够扶助定义好多不等门类的vector,他们的不一样在于所满含的要素类型。

2.模版元基本概念

  模版元程序由元数据和元函数组成,元数据便是元编制程序能够操作的数量,即C++编写翻译器在编写翻译期能够操作的数目。元数据不是运营期变量,只好是编写翻译期常量,不可能改改,不足为怪的元数据有enum枚举常量、静态常量、基本类型和自定义类型等。

  元函数是模板元编制程序中用来操作管理元数据的“零件”,能够在编写翻译期被“调用”,因为它的功力和款式和平运动转时的函数相像,而被称呼元函数,它是元编制程序中最要紧的构件。元函数实际表现为C++的叁个类、模板类或模板函数,它的司空眼惯花样如下:

template<int N, int M>
struct meta_func
{
    static const value = N+M;
}

  调用元函数到手value值:cout<<meta_func<1, 2>::value<<endl;

  meta_func的推行进度是在编写翻译期实现的,实际实行顺序时,是还没测算动作而是直接运用编写翻译期的测算结果的。元函数只管理元数据,元数据是编写翻译期常量和种类,所以上面包车型地铁代码是编写翻译但是的:

int i = 1, j = 2;
meta_func<i, j>::value; //错误,元函数无法处理运行时普通数据

  模板元编制程序发生的源程序是在编写翻译期推行的次序,因而它首先要信守C++和模板的语法,可是它操作的靶子不是运营时平常的变量,由此不能采取运维时的C++关键字(如if、else、for),可用的语法成分格外简单,最常用的是:

  • enum、static const,用来定义编写翻译期的卡尺头常量;
  • typedef/using,用于定义元数据;
  • T、Args...,评释元数据类型;
  • template,首要用来定义元函数;
  • "::",域运算符,用于深入解析类型作用域获取计算结果(元数据)。

一经模板元编制程序中供给if-else、for等逻辑时该怎么做呢?

模板元中的if-else能够通过type_traits来兑现,它不只可以够在编写翻译期做判别,仍是可以够做计算、查询、转变和甄选。

模板元中的for等逻辑能够通过递归、重载、和模板特化(偏特化)等措施实现。

下边来拜访C++11提供的模版元功底库type_traits。

while   (eai1.MoveNext()   &&   eai2.MoveNext()   &&   eai3.MoveNext())
{
a(eai1.Current,   eai2.Current,eai3.Current);
}
}
//使用
public   static   void   CaculThem(int   v1,   int   v2,int   r)   {
        r=v1*v2;
}
Caculate_Contain(ai1,   ai2,   ai3,   new   alg <int,   int,   int> (CaculThem));
public   static   void   CaculThem2(float   v1,   int   v2,double   r)   {
        r=v1*v2;
}
Caculate_Contain(af1,   ai2,   ad3,   new   alg <float,   int,   double> (CaculThem2));
本人利用了叁个信托,作为传递管理容器成分的算法的载体。使用时,用实际的算法创造委托的实例。但实际的算法CaculThem(State of Qatar必需同相应的器皿成分类型一致。
上边轮到C++:
template <typename   C1,   typename   C2,   typename   C3,   typename   Alg>
Caculate_Container(const   C1&   c1,   const   C2&   c2,   C3&   c3,   Alg   a)
{
transform(c1.begin(),   c1.end(),   c2.begin(),   c3.begin(),   a);
}
//使用
template <typename   T1,   typename   T2,   typename   R>
R   mul_them(T1   v,T2   u)   {
return v*u;
}
Caculate_Container(ai1,   ai2,   ai3,   mul_them <int,   int,   int> );
Caculate_Container(af1,   ad2,   ad3,   mul_them <float,   double,   double> );
假若容器元素有所退换,C#代码必得重写算法CaculThem(卡塔尔。但C++不须要,由于mul_them <> (卡塔尔国本人是个函数模板,那么只需将这么些函数模板用新的项目实例化一下就可以。
C++的代码相对轻便些,灵活性也越来越高些。但那还不是百分百,C++还会有贰个末段极的解法,无需循环,无需创制模板算法,不供给写操作函数:
transform(c1.begin(),   c1.end(),   c2.begin(),   c3.begin(),   _1*_2);

模板是泛型编制程序的底工。

3.type_traits

  type_竞博体育官方版下载 ,traits是C++11提供的沙盘模拟经营元底蕴库,通过type_traits能够兑今后编写翻译期总计、查询、判定、调换和筛选,提供了模版元编制程序供给的一部分常用元函数。下边来会见一些为主的type_traits的主导用法。

  最简便易行的叁个type_traits是概念编写翻译期常量的元函数integral_constant,它的概念如下:

template< class T, T v >
struct integral_constant;

  依附那一个简单的trait,我们得以很方便地定义编写翻译期常量,举例定义一个值为1的int常量可以这么定义:

using one_type = std::integral_constant<int, 1>;

或者

template<class T>
struct one_type : std::integral_constant<int, 1>{};

  获取常量则经过one_type::value来获取,这种概念编写翻译期常量的法子比较C++98/03要简明,在C++98/03中定义编写翻译期常量日常是那样定义的:

template<class T>
struct one_type
{
    enum{value = 1};
};

template<class T>
struct one_type
{
    static const int value = 1;
};

  可以观望,通过C++11的type_traits提供的一个简单易行的integral_constant就足以很方便的概念编写翻译期常量,而不供给再去通过定义enum和static const变量格局去定义编写翻译期常量了,那也为定义编写翻译期常量提供了其它一种方法。C++11的type_traits已经提供了编写翻译期的true和false,是通过integral_constant来定义的:

typedef  integral_constant<bool, true> true_type;
typedef  integral_constant<bool, false> false_type;

  除了这一个基本的元函数之外,type_traits还提供了足够的元函数,举例用来编译期判定的元函数:

#include <iostream> #include <type_traits> int main() { std::cout << "int: " << std::is_const<int>::value << std::endl; std::cout << "const int: " << std::is_const<const int>::value << std::endl; //判别类型是还是不是雷同 std::cout<< std::is_same<int, int>::value<<"n";// true std::cout<< std::is_same<int, unsignedint>::value<<"n";// false //添加、移除const cout << std::is_same<const int, add_const<int>::type>::value << endl; cout << std::is_same<int, remove_const<const int>::type>::value << endl; //增添援用 cout << std::is_same<int&, add_lvalue_reference<int>::type>::value << endl; cout << std::is_same<int&&, add_rvalue_reference<int>::type>::value << endl; //取公共项目 typedef std::common_type<unsigned char, short, int>::type NumericType; cout << std::is_same<int, NumericType>::value << endl; return 0; }

  type_竞博体育官方版下载  元函数是模板元编程中用于操作处理元数据的,在泛型编程中。traits还提供了编写翻译期选择traits:std::conditional,它在编写翻译期依照贰个决断式接纳三个系列中的一个,和法规表明式的语义相符,近似于三个长富表明式。它的原型是:

template< bool B, class T, class F >
struct conditional;

用法比较轻松:

#include <iostream>
#include <type_traits>

int main() 
{
    typedef std::conditional<true,int,float>::type A;               // int
    typedef std::conditional<false,int,float>::type B;              // float

    typedef std::conditional<(sizeof(long long) >sizeof(long double)),
    long long, long double>::type max_size_t;

    cout<<typeid(max_size_t).name()<<endl;  //long double
}

  此外多少个常用的type_traits是std::decay(朽化卡塔尔,它对于视而不见品种来讲std::decay(朽化)是移除引用和cv符,大大简化了我们的书写。除了听而不闻档期的顺序之外,std::decay还是能够用于数组和函数,具体的转变准绳是那般的:

  先移除T类型的引用,获得类型U,U定义为remove_reference<T>::type。

  • 如果is_array<U>::value为 true,修正类型type为remove_extent<U>::type *。
  • 否则,如果is_function<U>::value为 true,改善类型type将为add_pointer<U>::type。
  • 要不然,修正类型type为 remove_cv<U>::type。

std::decay的骨干用法:

typedef std::decay<int>::type A;           // int
typedef std::decay<int&>::type B;          // int
typedef std::decay<int&&>::type C;         // int
typedef std::decay<constint&>::type D;    // int
typedef std::decay<int[2]>::type E;        // int*
typedef std::decay<int(int)>::type F;      // int(*)(int)

  std::decay除了移除普通品种的cv符的功力之外,还足以将函数类型调换为函数指针类型,进而将函数指针变量保存起来,以便在后边延迟实行,举例下边包车型地铁事例。

template<typename F>
struct SimpFunction
{
    using FnType = typename std::decay<F>::type;//先移除引用再添加指针

    SimpFunction(F& f) : m_fn(f){}

    void Run()
    {
        m_fn();
    }

    FnType m_fn;
};

  借使要保留输入的函数,则先要获取函数对应的函数指针类型,这时候就能够用std::decay来博取函数指针类型了,using FnType = typename std::decay<F>::type;达成函数指针类型的概念。type_traits还提供了收获可调用对象回来类型的元函数:std::result_of,它的主干用法:

int fn(int) {return int();}                            // function
typedef int(&fn_ref)(int);                             // function reference
typedef int(*fn_ptr)(int);                             // function pointer
struct fn_class { int operator()(int i){return i;} };  // function-like class

int main() {
  typedef std::result_of<decltype(fn)&(int)>::type A;  // int
  typedef std::result_of<fn_ref(int)>::type B;         // int
  typedef std::result_of<fn_ptr(int)>::type C;         // int
  typedef std::result_of<fn_class(int)>::type D;       // int
}

  type_traits还提供了一个很有用的元函数std::enable_if,它选拔SFINAE(substitude failure is not an error卡塔尔本性,依照标准选拔重载函数的元函数std::enable_if,它的原型是:

template<bool B, class T = void> struct enable_if;

  根据enable_if的字面意思就足以知道,它使得函数在认清规范B仅仅为true时才有效,它的着力用法:

template <class T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type foo(T t)
{
    return t;
}
auto r = foo(1); //返回整数1
auto r1 = foo(1.2); //返回浮点数1.2
auto r2 = foo(“test”); //compile error

  在上头的例子中对模板参数T做了节制,即只可以是arithmetic(整型和浮点型)类型,若是为非arithmetic类型,则编写翻译不通过,因为std::enable_if只对满足判定式条件的函数有效,对此外函数无效。

  能够通过enable_if来兑现编写翻译期的if-else逻辑,比方上边包车型地铁例证通过enable_if和准星判别式来将入参分为两大类,进而满意全体的入参类型:

template <class T>
typename std::enable_if<std::is_arithmetic<T>::value, int>::type foo1(T t)
{
    cout << t << endl;
    return 0;
}

template <class T>
typename std::enable_if<!std::is_arithmetic<T>::value, int>::type foo1(T &t)
{
    cout << typeid(T).name() << endl;
    return 1;
}

  对于arithmetic类型的入参则重临0,对于非arithmetic的连串则赶回1,通过arithmetic将享有的入参类型分成了两大类进行管理。从地方的例子还是能够看看,std::enable_if能够完成强盛的重运载飞机制,因为平时来讲必得是参数不一致本领重载,要是独有再次来到值分歧是无法重载的,而在上头的例子中,重返类型形似的函数都足以重载。

  C++11的type_traits提供了近百个在编写翻译期总括、查询、决断、调换和抉择的元函数,为我们编辑元程序提供了一点都不小的方便人民群众。假使说C++11的type_traits让模版元编制程序变得轻便,那么C++11提供的可变模板参数和tuple则更是提升了模版元编制程序。

没看精通?小编一开始也看不知底。这里运用了boost库的拉姆da表达式。_1占位符对应c1的因素,_2的占位符对应c2的成分,_1*_2意味着才c1的成分乘上c2的因素,其结果放在c3里。表明式能够写得更复杂,比如(_1*_2+3*_1)/(_1-_2卡塔尔(قطر‎。Lambda表达式能够用在具有须求操作的算法中,比方小编要去掉字符串中的“-”,能够这么写:
remove_if(s.begin(),   s.end(),   _1==’-’);
Lambda表达式基于一种名称为“模板表达式”的技能,通过操作符重载,将贰个表明式一层一层地进行,构成三个剖判树。然后作为一个函数对象传递给算法,算法在循环内调用函数对象,试行相应的测算。
从比不上那更简便的了啊。原理是够复杂的,但大家可以完全不理睬当中复杂的原理,只管用正是了。别看只是叁个渺小算法,要明了,再庞大的软件(象JSF的代码有一九〇二万行之多)都是由那一个渺小的算法构成的。C++提供的算法和简化算法使用的库大约对持有的顺序算法皆有帮助,不仅对这种底层算法有效,在越来越高档期的顺序的算法功能更加大。
这边本人就不再给出C#的代码了,因为C#还不支持Lambda表明式,也力无法支模拟。假如想要的话,等C#3.0吧。
好了,应该是做总括的时候了。从下边包车型客车那些事例可以看出,在最大旨的语句上,C#有时比C++简单些,因为C#提供了越来越多的第一字。可是,随着算法的逐级复杂,C++的肤浅本事逐年发挥成效。一旦需求树立抽象的算法和代码时,C++的泛型编制程序本领立即产生出巨大的能量。最终,大家应用boost::lambda库最大限度简化了算法的行使。更器重的,拉姆da表明式的落实特别犬牙交错,不过使用却不行轻松。
那就是始于所说的:“C++之所以复杂,是为了选拔起来更简便易行”那句话的意义。C++提供的那个复杂的建制,是为了创设库,以提供语言未有兑现的功用,这一个职能可以小幅简化开垦工作。如标准Curry的容器、算法,boost库的拉姆da表明式、BGL的命名参数、智能指针等等。
也即是说,三个程序猿能够独有学习最宗旨的C++编制程序技术,便足以选取现有的种种库开荒软件。只管用,别问为何。在此种情景下,学习和平运动用C++的难度同C#和Java比较未有本质的歧异。但出于C++能够提供越来越灵活火速的库,在超级多动静下,反而比C#和Java更好用。
要完结这种程度,技士所需的练习的确会比C#和Java多局地。所需的训练主要集中在:标准库的行使;分化对象、指针和引用;指针、内部存款和储蓄器、财富的拍卖方法,智能指针的使用;类应用的有个别特别要点(布局函数、隐式调换等等);多态的正确管理;模板的用法。别的还亟需给学习者定下一些“规矩”,幸免误用一些敏感的语言机制。这一个“规矩”只需信守,不要问为什么。一旦那个“规矩”成了本能的一有的(深化操练能够高达这种效用),程序猿半熟了。纵然回过头使用C#或Java,也能非常轻易产生违害就利,博采有益的意见。(要小心,那时技术员很或然会骂人的。作者是个相比Sven的人,通常不骂人,除了驾乘的时候和使用C#的时候)。
这几个剧情一经编排妥帖,用法标准,学习者无需费用很短的大运就可以调控,大约两七个月就能够,如有7个月的年月,便得以熟稔。那样练习出来的技师根基十二分实在,无论以往攻读如何语言或本事,都可以手到擒来。假若他还爱怜C++,那么能够更进一层深造C++的高档机制,参与库开拓者的行列。
对于自读书人,也得以展开那样的教练。但不得不要科学抉择教材。Lippman的《essential   C++》和《C++   Primer》是本人所知的最棒的教科书。认真地遵循书中的线路,一再抓好操练,就能够了。入门书是很关键的,千万不要选用那么些C(++卡塔尔的书(大半本C,带上那么一丝丝C++的剧情)。
这么,我们实乃将C++的运用分成了八个层面,二个是行使范围,另二个是根底开辟规模。当然,底蕴开垦规模仍是可以分成应用性的底蕴开拓,举例帐务管理种类专项使用的底子库;和工具库的支付,象boost之类的库。应用规模的开辟人士不必明白(甚至足以不亮堂)C++全部的奇特天性,以至那一个库的中间机制,只需学会运用就能够。要达到规定的标准这种要求,小编言从计纳对能够学会高等数学的人来说,是举手之劳的。
对此四个应用C++的铺面,不供给各种程序员熟识全数的C++特性。有一点点权威潜心于公司级的库的兼顾和花费,而此外的技师只需达到地方所说的底子C++的水准。这种布置可以博得超级高的支付功效。但前提是技士选择规范的、扎实的根基C++培养演练,那在前面已经讲过了。
再重申叁遍,“规矩”是第一的。因为C#和Java命理术数,首要得益于去掉了超多危殆(但并非常平价和注重)的言语机制。在C++的教学中引进严苛的“规矩”,是在保存这几个危急但尊敬的建制的还要,使学习者防止其损害的手腕。必得让初大方知道许多机制是不可能碰的,因为那么些机制不是给他俩用的,是给那些足够明白其毁伤的人用的。
相比较,在路上驾驶压死人要比在C++中犯错误轻易得多(终归C++不会在马路上乱窜)。之所以大家并未每一日出事故,是因为大家遵照了平整(交规和驾乘本事)。C++编制程序也长久以来(任何编制程序都如此)。但不常候大家得以在受控的情形下合理违反交规,举例实践职务的警车和救护车能够逆向行驶、闯红灯。明显这是有规范的,开车员受过特训,鸣笛和亮警报灯。就疑似在C++中,独有受过特训,在有怜惜的情形下方能动用象placement   new那样的超危急机制同样。
准确,固然如此,C++的学习依旧不是二个安闲自得的进度,所学的剧情也比C#和Java的多。不过在提交的同期,还应看来收获。学习C++的获取不止是能够动用Lambda表明式那样高贵的语法,并且能够真的地左右编制程序手艺中的主题,为今后的升华打好根基。当您可以见到熟悉地使用规范库时,正宗的虚幻思维的手艺已经暗中地潜入了您的骨髓,那是形成一名卓越的软件设计员的着力尺度,何人不想要呢。
好了,作者这里显得了C++易用性的冰山一角,解释了难学难用的由来,也提供了学好C++的不二秘籍。“C#和Java比C++容易学习和应用”那句话固然不算中伤,但也是被严重地过甚其辞了。笔者盼望大家能象Discovery   Channel的Mythbusters(Discovery的保留节目,特地用试验的章程查看流言的切实地工作)那样,勇于尝试和施行,用本身的切身感知打破关于C++的流言流言。

泛型编制程序与面向对象编制程序相通,都依附于某种情势的多态性,面向对象编制程序注重的多态性称为运维时多态性,泛型编制程序信赖的多态性称为编写翻译时多态性或参数多态性。对于运营时多态性,只要使用基类的援用或指针,基类类型或派生类类型的指标就足以选取同一的代码。在泛型编制程序中,我们所编纂的函数和类能够多态的用来超出编写翻译时不相干的门类。一个类和一个函数可以用来调节三体系型的靶子。

4.可变模板参数

  C++11的可变模版参数(variadic templates)是C++11猛增的最有力的表征之一,它对参数进行了冲天泛化,它能表示0到任性个数、任性档期的顺序的参数。关于它的用法和行使本事读者能够参见小编在技士二〇一四年12月A上的篇章:泛化之美--C++11可变模版参数的妙用,这里不再赘言,这里将要体现的怎样依据可变模板参数实现部分编写翻译期算法,比方获取最大值、剖断是不是带有了有个别项目、依照目录查找类型、获取项目标目录和遍历类型等算法。达成那些算法供给组合type_traits或其余C++11特征,上边来看看那一个编写翻译期算法是哪些促成的。

  编写翻译期从二个整形连串中赢得最大值:

//获取最大的整数
template <size_t arg, size_t... rest>
struct IntegerMax;

template <size_t arg>
struct IntegerMax<arg> : std::integral_constant<size_t, arg>
{
};

template <size_t arg1, size_t arg2, size_t... rest>
struct IntegerMax<arg1, arg2, rest...> : std::integral_constant<size_t, arg1 >= arg2 ? IntegerMax<arg1, rest...>::value :
    IntegerMax<arg2, rest...>::value >
{
};

  那一个IntegerMax的兑现利用了type_traits中的std::integral_const,它在张开参数包的长河中,不断的可比,直到全数的参数都相比完,最后std::integral_const的value值即为最大值。它的利用很简短:

cout << IntegerMax<2, 5, 1, 7, 3>::value << endl; //value为7

  大家得以在IntegerMax的幼功上轻便的落到实处获取最大内部存款和储蓄器对齐值的元函数Max阿里gn。

  编写翻译期获取最大的align:

template<typename... Args>
struct MaxAlign : std::integral_constant<int, IntegerMax<std::alignment_of<Args>::value...>::value>{};
cout << MaxAlign<int, short, double, char>::value << endl; //value为8
    编译判断是否包含了某种类型:
template < typename T, typename... List >
struct Contains;

template < typename T, typename Head, typename... Rest >
struct Contains<T, Head, Rest...>
    : std::conditional< std::is_same<T, Head>::value, std::true_type, Contains<T, Rest... >> ::type{};

template < typename T >
struct Contains<T> : std::false_type{};
用法:cout<<Contains<int, char, double, int, short>::value<<endl; //输出true

  那么些Contains的达成接收了type_traits的std::conditional、std::is_same、std::true_type和std::false_type,它的落到实处思路是在开展参数包的历程中不仅仅的比较类型是或不是近似,假如同样则设置值为true,不然设置为false。

        编译期获取项指标目录:

template < typename T, typename... List >
struct IndexOf;

template < typename T, typename Head, typename... Rest >
struct IndexOf<T, Head, Rest...>
{
    enum{ value = IndexOf<T, Rest...>::value+1 };
};

template < typename T, typename... Rest >
struct IndexOf<T, T, Rest...>
{
    enum{ value = 0 };
};

template < typename T >
struct IndexOf<T>
{
    enum{value = -1};
};

  用法:cout<< IndexOf<int, double, short, char, int, float>::value<<endl; //输出3

  那个IndexOf的兑现比较轻便,在展开参数包的历程中看是不是合作到特化的IndexOf<T, T, Rest...>,若是相配上则结束递归将事情发生从前的value累积起来取得目的项指标目录地方,不然将value加1,假设具有的项目中都尚未相应的品种则赶回-1;

  编译期根据目录地点查找类型:

template<int index, typename... Types>
struct At;

template<int index, typename First, typename... Types>
struct At<index, First, Types...>
{
    using type = typename At<index - 1, Types...>::type;
};

template<typename T, typename... Types>
struct At<0, T, Types...>
{
    using type = T;
};
    用法:
using T = At<1, int, double, char>::type;
    cout << typeid(T).name() << endl; //输出double

  At的得以达成比较轻易,只要在进展参数包的进程中,不断的将索引依次减少至0时完毕就可以得到对应索引地方的品种。接下来看看哪些在编写翻译期遍历类型。

template<typename T>
void printarg()
{
    cout << typeid(T).name() << endl;
}

template<typename... Args>
void for_each() 
{
    std::initializer_list<int>{(printarg<Args>(), 0)...};
}
用法:for_each<int,double>();//将输出int double

  这里for_each的完毕是经过初阶化列表和逗号表达式来遍历可变模板参数的。

  能够看看,依据可变模板参数和type_traits以至模板偏特化和递归等方法大家得以兑现部分可行的编写翻译期算法,那么些算法为我们编辑应用层级其他代码奠定了基础,前面模板元编制程序的求实运用团长会用到这一个元函数。

  C++11提供的tuple让大家编辑模版元程序变得更加灵敏了,在早晚水准上坚实了C++的泛型编制程序手艺,下边来看看tuple怎么着使用于元程序中的。

在C++中,模板是泛型编制程序的底蕴,模板是开创类或函数的蓝图或公式。如标准库定义了二个类模板vector,能够发生率性数量的一定项指标vector类,如vector<int>和vector<string>。

5.tuple与模版元

  C++11的tuple本身正是一个可变模板参数组成的元函数,它的原型如下:

template<class...Types>
class tuple;

  tuple在模板元编程中的叁个运用场景是将可变模板参数保存起来,因为可变模板参数不能够直接当作变量保存起来,供给依靠tuple保存起来,保存之后再在急需的时候经过一些花招将tuple又改动为可变模板参数,这一个进程有一些相似于化学中的“氧化还原反应”。看看上边包车型大巴例证中,可变模板参数和tuple是何等互相调换的:

//定义整形序列
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;
};

template<typename... Args>
void printargs(Args... args){
    //先将可变模板参数保存到tuple中
    print_helper(typename MakeIndexes<sizeof... (Args)>::type(), std::make_tuple(args...));
}

template<int... Indexes, typename... Args>
void print_helper(IndexSeq<Indexes...>, std::tuple<Args...>&& tup){
    //再将tuple转换为可变模板参数,将参数还原回来,再调用print
    print(std::get<Indexes>(tup)...); 
}
template<typename T>
void print(T t)
{
    cout << t << endl;
}

template<typename T, typename... Args>
void print(T t, Args... args)
{
    print(t);
    print(args...);
}

  用法:printargs(1, 2.5, “test”); //将输出1 2.5 test

  上边包车型客车事例print实际上是出口可变模板参数的剧情,具体做法是先将可变模板参数保存到tuple中,然后再通过元函数MakeIndexes生成一个整形类别,这么些整形种类正是IndexSeq<0,1,2>,整形系列代表了tuple中元素的目录,生成整形系列之后再调用print_helper,在print_helper中张开这些整形系列,张开的进程中依照实际的目录从tuple中获取相应的要素,最终将从tuple中抽出来的元素构成四个可变模板参数,进而达成了tuple“还原”为可变模板参数,最后调用print打字与印刷可变模板参数。

  tuple在模板元编程中的其它二个运用处景是用来得以达成部分编译期算法,比如大范围的遍历、查找和集结等算法,达成的思绪和可变模板参数完结的编写翻译期算法相仿,关于tuple相关的算法,读者能够参谋笔者在github上的代码:

  上边来拜候模版元的具体使用。

2、模板定义

6.模版元的施用

  大家将显得什么通过沙盘元来贯彻function_traits和Vairant类型。

  function_traits用来获取函数语义的可调用对象的局地品质,举例函数类型、再次来到类型、函数指针类型和参数类型等。上面来走访哪些落到实处function_traits。

template<typename T>
struct function_traits;

//普通函数
template<typename Ret, typename... Args>
struct function_traits<Ret(Args...)>
{
public:
    enum { arity = sizeof...(Args) };
    typedef Ret function_type(Args...);
    typedef Ret return_type;
    using stl_function_type = std::function<function_type>;
    typedef Ret(*pointer)(Args...);

    template<size_t I>
    struct args
    {
        static_assert(I < arity, "index is out of range, index must less than sizeof Args");
        using type = typename std::tuple_element<I, std::tuple<Args...>>::type;
    };
};

//函数指针
template<typename Ret, typename... Args>
struct function_traits<Ret(*)(Args...)> : function_traits<Ret(Args...)>{};

//std::function
template <typename Ret, typename... Args>
struct function_traits<std::function<Ret(Args...)>> : function_traits<Ret(Args...)>{};

//member function
#define FUNCTION_TRAITS(...) 
    template <typename ReturnType, typename ClassType, typename... Args>
    struct function_traits<ReturnType(ClassType::*)(Args...) __VA_ARGS__> : function_traits<ReturnType(Args...)>{}; 

FUNCTION_TRAITS()
FUNCTION_TRAITS(const)
FUNCTION_TRAITS(volatile)
FUNCTION_TRAITS(const volatile)

//函数对象
template<typename Callable>
struct function_traits : function_traits<decltype(&Callable::operator())>{};

  由于可调用对象或者是兴味索然的函数、函数指针、lambda、std::function和成员函数,所以大家供给针对那几个项目分别做偏特化。在那之中,成员函数的偏特化稍稍复杂一点,因为涉及到cv符的管理,这里经过定义三个宏来消亡重复的沙盘类定义。参数类型的拿走大家是依赖tuple,将参数调换为tuple类型,然后依照目录来收获相应品种。它的用法比较轻易:

template<typename T>
void PrintType()
{
    cout << typeid(T).name() << endl;
}
int main()
{
    std::function<int(int)> f = [](int a){return a; };
    PrintType<function_traits<std::function<int(int)>>::function_type>(); //将输出int __cdecl(int)
    PrintType<function_traits<std::function<int(int)>>::args<0>::type>();//将输出int
    PrintType<function_traits<decltype(f)>::function_type>();//将输出int __cdecl(int)
}

  有了那一个function_traits和日前实现的局地元函数,大家就会造福的贯彻三个“万能类型”—Variant,Variant实际上叁个泛化的项目,那么些Variant和boost.variant的用法相近。boost.variant的基本用法如下:

typedef variant<int,char, double> vt;
vt v = 1;
v = 'a';
v = 12.32;

  那些variant能够承担已经定义的那贰个类型,看起来有一些相近于c#和java中的object类型,实际上variant是擦除了项目,要博取它的实际上类型的时候就稍显麻烦,供给通过boost.visitor来访谈:

竞博体育官方版下载 1struct VariantVisitor : public boost::static_visitor<void> { void operator() (int a) { cout << "int" << endl; } void operator() (short val) { cout << "short" << endl; } void operator() (double val) { cout << "double" << endl; } void operator() (std::string val) { cout << "string" << endl; } }; boost::variant<int,short,double,std::string> v = 1; boost::apply_visitor(visitor, v); //将输出int View Code

  通过C++11模板元实现的Variant将修正值的获取,将赢得实际值的秘诀改为停放的,即透过上面包车型客车章程来做客:

typedef Variant<int, double, string, int> cv;
cv v = 10;
v.Visit([&](double i){cout << i << endl; }, [](short i){cout << i << endl; }, [=](int i){cout << i << endl; },[](const string& i){cout << i << endl; });//结果将输出10

  这种格局更有益于直观。Variant的贯彻内需重视前文中落到实处的片段元函数MaxInteger、MaxAlign、Contains和At等等。下边来探问Variant完毕的尤为重要代码,完整的代码请读者参谋小编在github上的代码

竞博体育官方版下载 2template<typename... Types> class Variant{ enum{ data_size = IntegerMax<sizeof(Types)...>::value, align_size = MaxAlign<Types...>::value }; using data_t = typename std::aligned_storage<data_size, align_size>::type; public: template<int index> using IndexType = typename At<index, Types...>::type; Variant(void) :m_typeIndex(typeid(void)){} ~Variant(){ Destroy(m_typeIndex, &m_data); } Variant(Variant<Types...>&& old) : m_typeIndex(old.m_typeIndex){ Move(old.m_typeIndex, &old.m_data, &m_data); } Variant(const Variant<Types...>& old) : m_typeIndex(old.m_typeIndex){ Copy(old.m_typeIndex, &old.m_data, &m_data); } template <class T, class = typename std::enable_if<Contains<typename std::remove_reference<T>::type, Types...>::value>::type> Variant(T&& value) : m_typeIndex(typeid(void)){ Destroy(m_typeIndex, &m_data); typedef typename std::remove_reference<T>::type U; new(&m_data) U(std::forward<T>(value)); m_typeIndex = type_index(typeid(U)); } template<typename T> bool Is() const{ return (m_typeIndex == type_index(typeid(T))); } template<typename T> typename std::decay<T>::type& Get(){ using U = typename std::decay<T>::type; if (!Is<U>()) { cout << typeid(U).name() << " is not defined. " << "current type is " << m_typeIndex.name() << endl; throw std::bad_cast(); } return *(U*)(&m_data); } template<typename F> void Visit(F&& f){ using T = typename Function_Traits<F>::template arg<0>::type; if (Is<T>()) f(Get<T>()); } template<typename F, typename... Rest> void Visit(F&& f, Rest&&... rest){ using T = typename Function_Traits<F>::template arg<0>::type; if (Is<T>()) Visit(std::forward<F>(f)); else Visit(std::forward<Rest>(rest)...); } private: void Destroy(const type_index& index, void * buf){ std::initializer_list<int>{(Destroy0<Types>(index, buf), 0)...}; } template<typename T> void Destroy0(const type_index& id, void* data){ if (id == type_index(typeid(T))) reinterpret_cast<T*>(data)->~T(); } void Move(const type_index& old_t, void* old_v, void* new_v) { std::initializer_list<int>{(Move0<Types>(old_t, old_v, new_v), 0)...}; } template<typename T> void Move0(const type_index& old_t, void* old_v, void* new_v){ if (old_t == type_index(typeid(T))) new (new_v)T(std::move(*reinterpret_cast<T*>(old_v))); } void Copy(const type_index& old_t, void* old_v, void* new_v){ std::initializer_list<int>{(Copy0<Types>(old_t, old_v, new_v), 0)...}; } template<typename T> void Copy0(const type_index& old_t, void* old_v, void* new_v){ if (old_t == type_index(typeid(T))) new (new_v)T(*reinterpret_cast<const T*>(old_v)); } private: data_t m_data; std::type_index m_typeIndex;//类型ID }; View Code

  完结Variant首先必要定义叁个十足大的缓冲区用来寄放差异的品类的值,那些缓类型冲区实际上正是用来擦除类型,分歧的门类都经过placement new在那一个缓冲区上创立对象,因为品种长度分裂,所以须要思考内部存款和储蓄器对齐,C++11刚巧提供了内部存款和储蓄器对齐的缓冲区aligned_storage:

template< std::size_t Len, std::size_t Align = /*default-alignment*/ >
struct aligned_storage;

  它的率先个参数是缓冲区的长短,第3个参数是缓冲区内部存款和储蓄器对齐的轻重,由于Varaint可以承当各样类型,所以我们供给获得最大的花色长度,有限援救缓冲区丰盛大,然后还要得到最大的内部存款和储蓄器对齐大小,这里我们透过前边实现的马克斯Integer和MaxAlign就足以了,Varaint中内部存款和储蓄器对齐的缓冲区定义如下:

enum
{
    data_size = IntegerMax<sizeof(Types)...>::value,
    align_size = MaxAlign<Types...>::value
};
using data_t = typename std::aligned_storage<data_size, align_size>::type; //内存对齐的缓冲区类型

  其次,大家还要实现对缓冲区的布局、拷贝、析交涉平运动动,因为Variant重新赋值的时候须求将缓冲区中原来的品类析构掉,拷贝布局和移动结构时则须求拷贝和平运动动。这里以析构为例,我们须求基于当下的type_index来遍历Variant的拥有品种,找到呼应的种类然后调用该项目标析构函数。

void Destroy(const type_index& index, void * buf)
    {
        std::initializer_list<int>{(Destroy0<Types>(index, buf), 0)...};
    }

    template<typename T>
    void Destroy0(const type_index& id, void* data)
    {
        if (id == type_index(typeid(T)))
            reinterpret_cast<T*>(data)->~T();
    }

  这里,大家由此发轫化列表和逗号表达式来进展可变模板参数,在开展的经过中搜索对应的体系,若是找到了则析构。在Variant布局时还须求小心四个细节是,Variant不能担任未有优先定义的品类,所以在构造Variant时,须要约束品种必须在预订义的类型范围在那之中,这里经过type_traits的enable_if来界定模板参数的等级次序。

template <class T,
    class = typename std::enable_if<Contains<typename std::remove_reference<T>::type, Types...>::value>::type> Variant(T&& value) : m_typeIndex(typeid(void)){
            Destroy(m_typeIndex, &m_data);
            typedef typename std::remove_reference<T>::type U;
            new(&m_data) U(std::forward<T>(value));
            m_typeIndex = type_index(typeid(U));
    }

  这里enbale_if的准则正是前面完毕的元函数Contains的值,当未有在预订义的门类中找到呼应的门类时,即Contains再次回到false时,编译期会报贰个编写翻译错误。

  最终还索要完毕内置的Vistit功效,Visit的完结内需先经过定义一体系的寻访函数,然后再遍历这么些函数,遍历进程中,剖断函数的率先个参数类型的type_index是或不是与当前的type_index雷同,假设相近则取稳当前项目的值。

template<typename F>
    void Visit(F&& f){
        using T = typename Function_Traits<F>::template arg<0>::type;
        if (Is<T>())
            f(Get<T>());
    }

    template<typename F, typename... Rest>
    void Visit(F&& f, Rest&&... rest){
        using T = typename Function_Traits<F>::template arg<0>::type;
        if (Is<T>())
            Visit(std::forward<F>(f));
        else
            Visit(std::forward<Rest>(rest)...);
    }

  Visit功效的达成利用了可变模板参数和function_traits,通过可变模板参数来遍历一五种的拜望函数,遍历进度中,通过function_traits来博取第叁个参数的类型,和Variant当前的type_index相似的则取值。为啥要收获访问函数第四个参数的档次呢?因为Variant的值是独一的,唯有叁个值,所以博得的拜望函数的率先个参数的体系正是Variant中积存的靶子的实际类型。

1.举例

7总结

  C++11中的一些特征比方type_traits、可变模板参数和tuple让模版元编制程序变得更简约也更苍劲,模版元编制程序固然功用强盛,但也比较复杂,要用好模版元,供给大家转移观念方法,在精晓主旨的议论的底工上,再认真讨论模版元的一些常用手艺,这么些技巧是有规律可循的,基本上都以经过重定义、递归和偏特化等手法来得以达成的,当大家对那个基本手艺很熟习的时候再组成不断地试行,相信对模版元编制程序就会到位“应付自如”了。

 

本文曾刊登于《程序猿》二〇一五年七月刊。转发请注解出处。

后记:本文的原委根本源于于笔者在公司里面培养训练的叁回课程,因为众两个人对C++11模板元编程精通得不深切,所以本人感到有必要拿出来分享一下,让更加多的人看见,就整合治理了刹那间发到程序猿杂志了,笔者言听计从读者看完事后对模版元会有完美透顶的问询。

1.概述 模版元编制程序(template metaprogram)是C++中最复杂也是威力最精锐的编制程序范式,它是一种能够成立和操纵程序...

1)int compare(const string &v1, const string &v2)

{

if(v1<v2) return -1;

if(v2<v1) return 1;

return 0;

}

2)int compare(const int &v1, const int &v2)

{

if(v1<v2) return -1;

if(v2<v1) return 1;

return 0;

}

(3) int compare(const double &v1, const double &v2)

{

if(v1<v2) return -1;

if(v2<v1) return 1;

return 0;

}

我们能够用函数重载来贯彻分化门类的可比,不过观看可见,上述3个函数唯有形参差异,函数体是近似的,大家得以用函数模板来兑现自由等级次序数据的相比。不只有是3种档案的次序,即便指望此函数用于未知类型,只可以用函数模板了。

2、函数模板定义

函数模板是八个独门于类型的额函数,可看成一种办法,发生一定的函数版本。

template <typename T>

int compare(const T &v1, const T &v2)

{

if(v1<v2) return -1;

if(v2<v1) return 1;

return 0;

}

模板定义以重要字template发轫,后接模板形参表,模板形参表是用尖括号扩住的四个或多少个模板形参的列表,形参之间以逗号分隔。

模板形参表无法为空。

3 模板形参表

模板形参能够是意味着项目标体系形参,也能够是意味常量表明式的非类型形参。

品类形参在关键字class或typename前面定义。非类型形参在类型表明符之后表明。

其间,在这里,关键字class和typename未有分别。

4 使用函数模板

运用函数模板时,编写翻译器会估量那几个模板实参绑定到形参,一旦编写翻译器鲜明了实际上的模板实参,就称它例化了函数模板的三个实例。

用实参的体系估摸出类型形参的连串。

如int a = 10; int b= 11;

compare(a, b);

变量a时int型,推出形参const T &v1中的T为int型,同理得到const T &v第22中学的形参为int。然后依据函数形参的花色推测出模板形参的档次,对应的typename T 中的T为int,然后把模版函数中有所的T都换做int。称为实例化了函数模板的一个实例。

5、 inline 函数模板

inline关键字要在模板形参表之后,重返类型在此之前,不能够放在紧要字template以前。如:

template <typename T> inline T min(const T&, const T&);

3、定义类模板

举例:

template <typename Type> class Queue

{

public:

Queue();

Type &front();

const Type &front() const;

void push(const Type &);

void pop();

bool empty() const;

private:

//.....

};

//成员函数的贯彻

template <typename Type> void Queue<Type>::push(const Type &v1)

{

//....

总结:

函数模板是创制算法库的基础。类模板是简历标准水库蓄水体量器和迭代器类型的幼功。

模板和虚函数应该是不可能掺杂在联合具名的。

本文出自 “李海川” 博客,请必得保留此出处

、基本概念 所谓泛型编制程序 : 以独立于此外特定项指标艺术编写代码 。使用泛型程序时,大家需求提供具体程序实例所操作的花色或值。标...

  • 首页
  • 电话
  • 软件