C++ traits学习笔记(二)
这是我学习traits翻译的第二篇文章,原文地址:http://accu.org/index.php/journals/442,有不当的地方请过路的朋友指教,谢谢!
翻译文章也怪不容易的,转载请注明出处,感谢!
不同的代码片段具有相同的结构,仅在实现细节方面有所不同,这是一种常见的情形。因此我们可以重用相同的部分,只对于有差别的那些细节,针对不同的需求进行实现。在C语言中,可以通过函数指针实现这一目标。比如C标准库中的qsort函数中那个函数指针(这句话在我看来真是相当风骚。。。以前只是无脑去用qsort,从来没有想过设计方面的事,不管你湿没湿,反正我湿了。。。读了linux的协议栈的部分代码之后,对这一点体会又有所加深。)或是C++里的虚函数。但这些方法增加了运行时的开销(我想这里应该是指函数指针和虚函数这两种方法,只有在运行时才能确定到底调用哪个函数)。
C++通过模板引入了泛型编程,搞定了运行时绑定这个问题,但是乍一看泛型这玩意似乎仍是无奈之举。毕竟同一个算法不可能在所有的数据结构上都有好的表现:比如,链表和数组排序方式是不同的,有序数据的查找也要快于无序数据。
于是traits来了。
Bjarne Stroustrup大神说:Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine "policy" or "implementation details".
trait是一类小对象,其目的是包含一些实现细节方面的信息,供其他对象和算法使用。
C和C++程序员一般会比较熟悉limits.h和float.h,这两个头文件决定了整型和浮点型的各种属性。C++程序员还会比较熟悉std::numeric_limits,乍一看这个类只是以不同的实现方式提供了与前两个头文件相同的功能,进一步解读这个类,可以发现traits的第一个优势:一致的接口。
如果使用limits.h和float.h,程序员必须记住类型的前缀(prefix)和特性(trait)。比如,DBL_MAX包含了double类型的最大值特性(我的理解是DBL即prefix,MAX就是trait)。但如果使用类似numeric_limits这样的traits类,就可以用numeric_limits<double>::max()来表示double类型的最大值。更为重要的是,甚至可以不必关心所要使用的类型。比如下面这个返回数组最大元素的简单模板函数:
1: template< class T >
2: T findMax(const T const * data, const size_t const numItems) {
3: // Obtain the minimum value for type T
4: T largest = std::numeric_limits< T >::min();
5: for(unsigned int i=0; i<numItems; ++i)
6: if (data[i] > largest)
7: largest = data[i];
8: return largest;
9: }
使用了numeric_limits之后,只要创建相应的特化模板,就可以把上面的模板扩展到任意的自定义类型。
下面说说如何创建自己需要的traits类。以boost的is_void trait为例。
首先定义一个实现默认行为的泛型模板。由于当具有类型时,都不是void,所以此时is_void::value应该为false,所以有:
1: template< typename T >
2: struct is_void{
3: static const bool value = false;
4: };
然后再对这个模板进行扩充,加入对void的特化。
1: template<>
2: struct is_void< void >{
3: static const bool value = true;
4: };
这样我们就有了一个完整的traits类型。用它可以判断任意一个作为模板参数传入的类型是否为void。
这回以boost::is_pointer为例。和刚才一样,先定义一个默认的模板。
1: template< typename T >
2: struct is_pointer{
3: static const bool value = false;
4: };
再对所有指针类型加入一个具体化的模板:
1: template< typename T >
2: struct is_pointer< T* >{
3: static const bool value = true;
4: };
接下来要考虑篇首提到的问题:如何用traits技术在编译时选择合适的算法?
通过下面的例子来说明。在下面的例子中,根据算法所操作的对象,在编译时选择是使用标准算法(即对象的类型不支持优化算法)还是优化算法(对象的类型支持优化算法)。
还是先创建一个默认的traits类,将其命名为supports_optimised_implementation,除了名字,这个traits类与is_void完全一样。即:
1: template<typename T>
2: struct supports_optimised_implementation{
3: static const bool value = false;
4: };
接下来在模板algorithm_selector中实现默认算法。在本例中,由于只在标准算法和优化算法之间进行选择,所以algorithm_selector使用bool类型进行参数化。如果要在更多算法之间进行选择的话,可以把模板参数类型换成int或者enum。这里当参数值为true时,表示使用优化算法。
1: template< bool b >
2: struct algorithm_selector {
3: template< typename T >
4: static void implementation( T& object )
5: {
6: //implement the alorithm operating on "object" here
7: }
8: };
1: template<>
2: struct algorithm_selector< true > {
3: template< typename T >
4: static void implementation( T& object ) {
5: object.optimised_implementation();
6: }
7: };
接下来给出供算法最终用户调用的泛型函数。注意,该函数调用algorithm_selector,而algorithm_selector用我们定义的supports_optimised_implementation traits类进行参数化。
1: template< typename T >
2: void algorithm( T& object ) {
3: algorithm_selector< supports_optimised_implementation< T >::value >::implementation(object);
4: }
假设现在有两个类ObjectA和ObjectB,A不支持优化算法,B支持。则有:
1: class ObjectB {
2: public:
3: void optimised_implementation() {
4: //...
5: }
6: };
还需要对supports_optimised_implementation加上针对B类的特化:
1: template<>
2: struct supports_optimised_implementation< ObjectB > {
3: static const bool value = true;
4: };
最后,当对模板进行实例化时:
1: int main(int argc, char* argv[]) {
2: ObjectA a;
3: algorithm( a );
4: // calls default implementation
5: ObjectB b;
6: algorithm( b );
7: // calls
8: // ObjectB::optimised_implementation();
9: return 0;
10: }
Over。
整理一下思路。实际上,用户调用的函数是algorithm,这个函数的用户唯一关心的是算法的功能。比如排序,用户希望,不管要排序的对象是什么类型,只要调用排序算法,就可以无差别实现各类对象的排序。因此,在调用算法时,肯定不希望函数的形式是类似algorithm(object, algorithm_type)这种,即用户自己还需要了解该对象适合使用哪种算法,如果是这样,用户还需要去了解对象的细节。因此在上面的例子中,在algorithm的内部进行了算法的选择。
posted on 2012-01-19 21:37 youthlion 阅读(1777) 评论(0) 编辑 收藏 举报