C++之:模板元编程(一)

一、概念

利用模板特化机制实现编译期条件选择结构,利用递归模板实现编译期循环结构,模板元程序则由编译器在编译期解释执行。

模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。

  模板是一种对类型进行参数化的工具;

  通常有两种形式:函数模板类模板

  函数模板针对仅参数类型不同的函数

  类模板针对仅数据成员成员函数类型不同的类。

  使用模板的目的就是能够让程序员编写与类型无关的代码。比如编写了一个交换两个整型int 类型的swap函数,这个函数就只能实现int 型,对double,字符这些类型无法实现,要实现这些类型的交换就要重新编写另一个swap函数。使用模板的目的就是要让这程序的实现与类型无关,比如一个swap模板函数,即可以实现int 型,又可以实现double型的交换。模板可以应用于函数和类。下面分别介绍。

  注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。

二、通式

1、函数模板通式

template <class 形参名,class 形参名,...> 返回类型 函数名(参数列表)
{
    ... //函数体
}

其中template和class是关键字,class可以用typename 关键字代替,在这里typename 和class没区别。

<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。比如swap的模板函数形式为

template <class T> void swap(T& a, T& b){}

当调用这样的模板函数时类型T就会被被调用时的类型所代替,比如swap(a,b)其中a和b是int 型,这时模板函数swap中的形参T就会被int 所代替,模板函数就变为swap(int &a, int &b)。而当swap(c,d)其中c和d是double类型时,模板函数会被替换为swap(double &a, double &b),这样就实现了函数的实现与类型无关的代码。

注意:

对于函数模板而言不存在 h(int,int) 这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行 h(2,3) 这样的调用,或者int a, b; h(a,b)。

2、类模板通式

template<class  形参名,class 形参名,…> class 类名{

// 类定义...
};

类模板和函数模板都是以template开始后接模板形参列表组成,模板形参不能为空,一但声明了类模板就可以用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明。比如

template<class T> class A{public: T a; T b; T hy(T c, T &d);};

在类A中声明了两个类型为T的成员变量a和b,还声明了一个返回类型为T带两个参数类型为T的函数hy。

对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如A<2> m;用这种方法把模板形参设置为int是错误的(编译错误:error C2079: 'a' uses undefined class 'A<int>'),类模板形参不存在实参推演的问题。也就是说不能把整型值2推演为int 型传递给模板形参。要把类模板形参调置为int 型必须这样指定A<int> m

在类模板外部定义成员函数的方法为:

template<模板形参列表> 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体}

比如有两个模板形参T1,T2的类A中含有一个void h()函数,则定义该函数的语法为:

template<class T1,class T2> void A<T1,T2>::h(){}

注意:当在类外面定义类的成员时template后面的模板形参应与要定义的类的模板形参一致。

再次提醒注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。

其中,template 是声明类模板的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个,可以是类型参数 ,也可以是非类型参数。类型参数由关键字 class或typename 及其后面的标识符构成。非类型参数由一个普通参数构成,代表模板定义中的一个常量。例:

template<class type,int width>

//type为类型参数,width为非类型参数
class Graphics;

注意:

(1)如果在全局域中声明了与模板参数同名的变量,则该变量被隐藏掉。

(2)模板参数名不能被当作类模板定义中类成员的名字。

(3)同一个模板参数名在模板参数表中只能出现一次。

(4)在不同的类模板或声明中,模板参数名可以被重复使用。

三、优劣及适用情况

通过将计算从运行期转移至编译期,在结果程序启动之前做尽可能多的工作,最终获得速度更快的程序。也就是说模板元编程的优势在于:

1.以编译耗时为代价换来卓越的运行期性能(一般用于为性能要求严格的数值计算换取更高的性能)。通常来说,一个有意义的程序的运行次数(或服役时间)总是远远超过编译次数(或编译时间)。

2.提供编译期类型计算,通常这才是模板元编程大放异彩的地方。

模板元编程技术并非都是优点:

1.代码可读性差,以类模板的方式描述算法也许有点抽象。

2.调试困难,元程序执行于编译期,没有用于单步跟踪元程序执行的调试器(用于设置断点、察看数据等)。程序员可做的只能是等待编译过程失败,然后人工破译编译器倾泻到屏幕上的错误信息。

3.编译时间长,通常带有模板元程序的程序生成的代码尺寸要比普通程序的大,

4.可移植性较差,对于模板元编程使用的高级模板特性,不同的编译器的支持度不同。

四、技术细节

模板元编程使用静态C++语言成分,编程风格类似于函数式编程,在模板元编程中,主要操作整型(包括布尔类型、字符类型、整数类型)常量和类型,不可以使用变量、赋值语句和迭代结构等。被操纵的实体也称为元数据(Metadata),所有元数据均可作为模板参数。

由于在模板元编程中不可以使用变量,我们只能使用typedef名字和整型常量。它们分别采用一个类型和整数值进行初始化,之后不能再赋予新的类型或数值。如果需要新的类型或数值,必须引入新的typedef名字或常量。

五、其他范例

// 主模板
template<int N>
struct Fib
{
    enum { Result = Fib<N-1>::Result + Fib<N-2>::Result };
};

// 完全特化版
template <>
struct Fib<1>
{
    enum { Result = 1 };
};

// 完全特化版
template <>
struct Fib<0>
{
    enum { Result = 0 };
};

int main()
{
    int i = Fib<10>::Result;
    // std::cout << i << std::endl;
} 
// 仅声明
struct Nil;

// 主模板
template <typename T>
struct IsPointer
{
    enum { Result = false };
    typedef Nil ValueType;
};

// 局部特化
template <typename T>
struct IsPointer<T*>
{
    enum { Result = true };
    typedef T ValueType;
};

// 示例
int main()
{
    cout << IsPointer<int*>::Result << endl;
    cout << IsPointer<int>::Result << endl;
    IsPointer<int*>::ValueType i = 1;
    //IsPointer<int>::ValueType j = 1;
    // 错误:使用未定义的类型Nil
}
//主模板
template<bool>
struct StaticAssert;

// 完全特化
template<>
struct StaticAssert<true>
{};

// 辅助宏
#define STATIC_ASSERT(exp)\
{ StaticAssert<((exp) != 0)> StaticAssertFailed; }

int main()
{
    STATIC_ASSERT(0>1);
}

参考资料

[1] http://www.cnblogs.com/salomon/archive/2012/06/04/2534787.html

[2] http://www.cnblogs.com/assemble8086/archive/2011/10/02/2198308.html

[3] http://www.cnblogs.com/gw811/archive/2012/10/25/2738929.html

时间: 2024-08-18 04:24:42

C++之:模板元编程(一)的相关文章

利用模板元编程实现解循环优化

简介 在<C++ Templates: The Complete Guide>一书中(以下简称书),提出了模板元编程最早的实际应用之一:在数值运算中进行解循环优化. 而本文的标题是噱头!本文的真正目的是指出这种优化措施在增加复杂性的同时,并不一定能明显改善效率.应当谨慎使用该技术--默认不使用该技术,在点积计算确实是效率瓶颈时考虑采用该技术,并认真测试该技术是否真能提高效率. 背景 数值运算库中常常需要提供向量点积(dot_product)运算.其定义用C++代码描述也许更清楚- templa

C++箴言:谨慎使用模板元编程

template metaprogramming (TMP)(模板元编程)是写 template-based(基于模板)的运行于编译期间的 C++ 程序的过程.考虑一下:一个 template metaprogram(模板元程序)是用 C++ 写的运行于 C++ 编译器中的程序.当一个 TMP 程序运行完成,它的输出--从 templates(模板)实例化出的 C++ 源代码片断--随后被正常编译. 如果你仅把它看作古怪的特性而没有打动你,那你就不会对它有足够的深入的思考. C++ 并不是为 t

Functional Programming与C++的模板元编程

先来看一个例子: #include <stdio.h> template <int depth> class Fibnacci { public: static const int value = Fibnacci<depth-1>::value + Fibnacci<depth-2>::value; }; template <> class Fibnacci<0> { public: static const int value =

初探模板元编程

关于模板元编程的知识也有所了解,相关的书籍也看过几本,但是至今还没有亲手写过一个模板元程序,原因就是没有一个合适的机会应用模板元编程技术,今天在CSDN上看见一个帖子,定义常量字符串 char *p="Hello,Word!"; 既然是常量字符串,应该可以在编译期知道p的长度,在编译期间如何得到? 这个问题,我首先想到用模板元编程来实现,于是尝试编写一个. 真正用模板元编程时,才发现自己完全没有思路,不知道如何解决.于是下定决心学好模板元编程,这个问题就是我学习的动力,希望以后能够使用

【C/C++学院】0816-引用包装器/仿函数/转义字符 R”()”/using别名/模板元编程 比递归优化/智能指针/多线程/静态断言以及调试技能的要求 assert

引用包装器  std::ref(变量) #include<iostream> template<class T> void com(T arg)//模板函数,引用无效,引用包装器 { std::cout <<"com ="<< &arg << "\n"; arg++; } void main() { int count = 10; int & rcount = count; com(coun

C++之:模板元编程(三) 默认模板参数

一.类模板的默认模板参数原则 1.可以为类模板的类型形参提供默认值,但不能为函数模板的类型形参提供默认值.函数模板和类模板都可以为模板的非类型形参提供默认值. 2.类模板的类型形参默认值形式为: template<class T1, class T2=int> class A{}; 为第二个模板类型形参T2提供int型的默认值. 3.类模板类型形参默认值和函数的默认参数一样,如果有多个类型形参则从第一个形参设定了默认值之后的所有模板形参都要设定默认值,比如 template<class

使用模板元编程快速的得到斐波那契数。。

这是一种将运行时消耗转移到编译器消耗的方法,是c++模板的一种应用. 当你的程序运行时效率需要特别高的时候,可以考虑这样的方法. 模板实例化的时候需要常量: #include <iostream> using namespace std; template < unsigned N > struct Fib { enum { Val = Fib<N-1>::Val + Fib<N-2>::Val //递归.. }; }; template<> /

C++之:模板元编程(二) 模板形参

一.模板形参概述 有三种类型的模板形参:类型形参,非类型形参和模板形参. 二.类型形参 2.1 .类型模板形参 类型形参由关见字class或typename后接说明符构成,如template<class T> void h(T a){};其中T就是一个类型形参,类型形参的名字由用户自已确定.模板形参表示的是一个未知的类型.模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同,即可以用于指定返回类型,变量声明等. 2.2. 不能为同一个模板类型形参指

引用内部函数绑定机制,R转义字符,C++引用,别名,模板元,宏,断言,C++多线程,C++智能指针

 1.引用内部函数绑定机制 #include<iostream> #include<functional>   usingnamespacestd; usingnamespacestd::placeholders;   //仿函数,创建一个函数指针,引用一个结构体内部或者一个类内部的共有函数 structMyStruct {    voidadd(inta)    {        cout <<a <<endl;    }    voidadd2(in