C++模板专门化与重载
2013-08-10 10:55 夜与周公 阅读(1057) 评论(1) 编辑 收藏 举报最近在复习C++有关知识,又重新看<<Effective C++>>,收获颇丰。原来以前看这边书,好多地方都是浅尝辄止。<<Effective C++>>条款25:考虑写出一个不抛出异常的swap函数,涉及到C++模板专门化(Templates Specialization)和函数重载(overloading)问题,而当重载与模板搅合在一起时,许多问题都变得“模棱两可”。
首先回顾<<Effective C++>>条款25:考虑写出一个不抛出异常的swap函数,想告诉我们什么东西。
swap函数为我们提供了异常安全编程的方法,以及用来作为处理自我赋值一种常见机制,因此实现一个不抛出异常的swap函数,变得相对重要起来。缺省情况下的swap函数的典型实现如下:
namespace std { template<typename T> void swap(T& a, T& b) { T temp(a); a = b; b = temp; } }
然后,对于模型数据类型其成员变量是指针,指向一个对象,保存了数据(pointer to implementation手法)。如果copying函数采用了deep copying方法,上面的代码将会非常低效,因为,只需要互换a与b指针即可。问题是,缺省版本swap对类型无法可知这些信息,因此针对上述情况,需要专门化swap函数。
1)如果T是class,可以先让T提供一个swap函数,完成swap功能,然后借由functhon template的全特化,实现专门化的swap函数:
class Widge { public: void swap(Wiget& other) { using std::swap(); swap(pImpl, other.pImpl); }
private:
WidetImpl* pImpl;
};
//为程序提供一个特化版本的swap:
namespace std
{
template<>
void swap<Widegt>(Widget& a, Widget& b)
{ a.swap(b);
}
}
上面的代码很好的与STL容器保持了一致性,因为STL容器也都提供了swap成员函数和std::swap特化版本。
2)如果Widget与WidgetImpl不是class,而是class template,特化版本的swap函数,我们可能想写成这样的形式:
namespace std { template<class T> void swap<Widegt<T>>(Widget<T>& a, Widget<T>& b) { a.swap(b); } }
然而这个代码却无法通过编译,C++不支持function template的偏特化,我们需要使用模板函数的重载技术:
namespace std { template<class T> void swap( Widget<T>& a, Widget<T>& b) //重载了function templates { a.swap(b); } }
问题似乎已经解决了,嗯,是的,还存在一个问题:用户可以全特化std内的templates,但是不能新的对象(template、function、class)。解决方法是将这些类与swap函数放到新的命名空间中,这边便独立与std命名空间。
--------------------------------------------------------------------华丽分割线--------------------------------------------------------------------
上面介绍的内容,涉及到以下的内容:1)模板函数;2)重载函数;3)全特化和偏特化。当这些东西交织在一起的时候,我们需要足够的耐心做区分甄别。
1)模板类、模板函数与重载
// Example 1: Class vs. function template, and overloading // // A class template template<typename T> class X { /*...*/ }; // (a) 类模板 // A function template with two overloads template<typename T> void f( T ); // (b) 函数模板 template<typename T> void f( int, T, double ); // (c) 函数模板重载
(a)、(b)、(c)均没有专门化,这些未被专门化的template又被称为基础基模板。
2)特化
template class可以有全特化与偏特化两种, template function仅能全特化。
// Example 1, continued: Specializing templates // // A partial specialization of (a) for pointer types template<typename T> class X<T*> { /*...*/ }; // A full specialization of (a) for int template<> class X<int> { /*...*/ }; // A separate base template that overloads (b) and (c) // -- NOT a partial specialization of (b), because // there's no such thing as a partial specialization // of a function template! template<class T> void f( T* ); // (d) // A full specialization of (b) for int template<> void f<int>( int ); // (e) // A plain old function that happens to overload with // (b), (c), and (d) -- but not (e), which we'll // discuss in a moment void f( double ); // (f)
当function template与重载搅合在一起的时候,就存在匹配哪个版本函数的问题,匹配规则如下:
1)首先查找non template function ,如果在这些函数中匹配成功,则匹配结束(first-class citizens)
2)否定,在base template function 中查找最匹配的函数,并实例化,如果base template function恰巧有提供全特化版本模板函数,则使用全特化版本(sencond-class citizens)
将以上两个规则运用的例子:
// Example 1, continued: Overload resolution // bool b; int i; double d; f( b ); // calls (b) with T = bool f( i, 42, d ); // calls (c) with T = int f( &i ); // calls (d) with T = int f( i ); // calls (e) f( d ); // calls (f)
最后一个问题:如何判断哪个base template function被specialization,再看下面的例子:
// Example 2: Explicit specialization // template<class T> // (a) a base template void f( T ); template<class T> // (b) a second base template, overloads (a) void f( T* ); // (function templates can't be partially // specialized; they overload instead) template<> // (c) explicit specialization of (b) void f<>(int*); // ... int *p; f( p ); // calls (c)
c是b是全特化,f(p)将会调用,符合人们的一般想法,但是,如果置换b与c的顺序,结果就不那么一样了:
// Example 3: The Dimov/Abrahams Example // template<class T> // (a) same old base template as before void f( T ); template<> // (c) explicit specialization, this time of (a) void f<>(int*); template<class T> // (b) a second base template, overloads (a) void f( T* ); // ... int *p; f( p ); // calls (b)! overload resolution ignores // specializations and operates on the base // function templates only
这个时候,c将是a的全特化(编译器没看到后面的b的定义)。按照配对规则,首先查找base template function最适合匹配的,b正好最为匹配,并且没有全特化版本,因此将会调用b。
重要准则:
1)如果我们希望客户化base template function,直接利用传统的函数形式,如果使用重载形式,那么请不要提供全特化版本。
2)如果正在编写一个base template function,不要提供特化和重载版本,将客户化定制功能下放给用户。实现方法是,在class template 同static 函数接口:
// Example 4: Illustrating Moral #2 // template<class T> struct FImpl; template<class T> void f( T t ) { FImpl<T>::f( t ); } // users, don't touch this! template<class T> struct FImpl { static void f( T t ); // users, go ahead and specialize this };
准则2的动机就是利用class template 特化与偏特化功能实现function 特化与偏特化功能。
参考文献:
<<Effective C++>>, Scott Meyers
<<Why Not Specialize Function Templates?>>, C/C++ Users Journal, 19(7), July 2001