C++模板的原理与应用

来源:互联网 发布:网络教育哪些学校好 编辑:程序博客网 时间:2024/05/22 12:57

相信大家对模板并不陌生,模板的基本概念我想就不用多说了。大多数人包括我自己对模板的理解就是“T容器”。

请看下面的代码:

template<intm1, int l1, int t1,int m2, int l2, intt2>

Physical<m1+m2,l1+l2, t1+t2> operator*(Physical<m1,l1, t1 > lhs, Physical<m2,l2, t2> rhs)

{

    return Physical<m1+m2,l1+l2, t1+t2>::unit*lhs.value() *rhs.value();

}

头一次见到template的这种用法时,我确实有点目眩。看来我们对模板的认识只是皮毛而已J

在当今的程序设计中模板已经显露出它的价值来了,比如STL全部都是以模板架构的。VS2003(VC7)以来的编译器也对模板提供了更多的支持。

我们需要对模板有更进一步的认识,下面就让我们进入模板的世界。

§1模板的特性

首先来看一个例子:

template<intn>

intFunc1()     { returnn; }

intFunc2(int n) { return n; }

你能看出这两个函数的区别在哪里吗?

它们的区别就在于,Func1的参数是编译期指定,如Func1<0>();而Func2的参数则是运行期指定。

这正体现了模板的特性或者说是它的技术核心,就是编译期的动态机制这种机制使程序在运行期具有更大的效率优势

到此你是不是对本文开头那段代码有了更深入的理解了呢?

模板的另一个特性是,如果一个模板没有被特化,那么编译器根本不会去理会它,也就是说模板内的代码被隐藏了。

§2函数模板与类模板

这是一个函数模板:template<class T> Func(T param) {}

函数模板的模板参数是隐式的,编译器会自动根据传入值的类型来确定模板参数的类型。因此函数模板的模板参数不能有默认值

这是一个类模板:template<class T> class MyClass {};

类模板的模板参数是显式的,使用一个模板类时必须指明其模板参数,因此类模板的模板参数可以有默认值

我们还可以做更多的事情,比如MyClass可以派生自TXTP界面库就是这么做的),在MyClass内部可以使用关于Tenumtypedef等等。这些将在下文一一谈到。

(我在这里提出一个建议,创建一个类模板,请记得第一件事就是对模板参数进行typedef定义。)

§3模板的部分特化与应用

模板最有价值的地方就是它的部分特化,也是应用最广泛的特性。

所谓“部分特化”也就是说,一个模板有多个参数,但我们只对其中一部分参数进行特化,或者是只针对常量模板参数的某种情况进行特化。

?编译期ASSERT

这是对bool型模板参数部分特化的一个例子。最简单的实现:

template<bool>struct CompileTimeAssert;

template<>struct CompileTimeAssert<true> {};

当我们将一个表达式作为模板参数,而这个表达式的值为false时,编译器就找不到合适的实现,便会报错了。

我们可以将它扩展一下:

template<bool>struct CompileTimeAssert {CompileTimeAssert(…) ;};  // 这里使用了C++支持的任意参数表

template<>struct CompileTimeAssert<false> {};

#defineCOMPILE_CHECK(expr,msg) /

{/

           class ERROR_##msg {}; /

            (void)CompileTimeAssert<((expr)!=0)>(ERROR_##msg()); /

}

   // 这里不直接传入expr是为了获得更大的适应性,因为expr有可能是无法隐式转换为bool型的(比如指针),笔者曾参与的

 // 一个项目在由VC6平台升级到VS2003平台时,由于后者ASSERT宏实现的变化,就遇到了这一问题。

当然,事实是编译期可用的表达式或函数(如sizeof__alignof数量上并不太多,但是这种方式是有着积极意义的。

?编译期分派与类型选择

常量映射为类型

请看这样一个模板:template<intv> struct Int2Type{ enum{ value = v }; };

模板参数的不同数值,就会产生不同类型的Int2Type。即Int2Type<0>不同于Int2Type<1>,以此类推。

我们可以利用这个模板实现编译期分派。看这样一个例子:

template<class T,bool bPolymorphic>

classMyClass

{

    ……

    void Func(T* pObj)

{

    if (bPolymorphic)

{

    T* pNewObj = pObj->Clone(); // 多态类型

……

}

else

{

    T* pNewObj = new T(*pObj); // 非多态类型

……

}

}

};

显然,编译器不会让你侥幸成功。而使用了Int2Type就不一样了:

template<class T,bool bPolymorphic>

classMyClass

{

    ……

voidFunc(T* pObj)

{

    Func(pObj, Int2Type< bPolymorphic >);

}

    private:

    void Func(T* pObj, Int2Type<true>)

{

T* pNewObj = pObj->Clone(); // 多态类型

……

}

voidFunc(T* pObj,Int2Type<false>)

{

T* pNewObj = new T(*pObj); // 非多态类型

……

}

};

 

类型映射为类型

也就是这样一个模板:template<typename T> struct Type2Type{typedef T    _MyType; };

其用法跟Int2Type类似,但我们可以利用它进而实现类型选择。

举一个例子,我们使用vector来存储数据,如果数据是非多态类型,存储其实体比较有效率;如果数据是多态类型,显然只能存储其指针。使用Type2Type实现如下:

template<boolflag, typename T,typename U> structTypeSelector { typedef T   _Result; };

template<typename T,typename U> structTypeSelector<false, T, U> {typedef U    _ Result; };

template<typename T,bool bPolymorphic>

classMyClass

{

    ……

    typedef TypeSelector< bPolymorphic, T*, T>::_Result    _MyType;

}

模板的部分特化与枚举的结合使用

这是一种具有极大的可扩展性的方式。比如我们要检查两个类型是否是相同的类型,可以这样实现:

template<class T,class U> struct CompileCheck { enum{ same_type = false }; };

template<class T>struct CompileCheck<T, T> {enum{ same_type =true }; };

使用这种方式还可以检查两个类型是否可以转换、是否可以双向转换、是否具有派生关系,可以判断一个类型是否是基本类型、属于那种基本类型、是否是指针,等等。当然这些都是在编译期进行的。请大家参阅附注中说明的原著,这里就不详述了。

一个另类的技巧

代码如下:

template<class T>struct UnConst {typedef T    _Result; };

template<>struct UnConst <const T> {typedef T    _ Result; };

template<class T>

classMyClass

{

    ……

    typedef UnConst <T>::_Result   _MyType;

}

看,我们巧妙的去掉了const修饰符。用同样的方法我们还可以去掉volatile修饰符。