C++ Traits Classes
参考博文
traits classes 的作用主要是用来为使用者提供类型信息。在 c++ 中,traits 习惯上总是被实现为 struct ,但它们往往被称为 traits classes。
为了清晰理解 traits 的原理,我们先来看 traits 使用的关键技术 —— 模板的特化与偏特化。
模板特化(template specialization)
所谓特化,就是将泛型的东西搞得具体化一些,从字面上来解释,就是为已有的模板参数进行一些使其特殊化的指定,使得以前不受任何约束的模板参数,受到特定的修饰,或完全被指定了下来。
我们先来看下一函数模板的通用定义:
template<typename t> struct my_is_void { static const bool value = false; };
然后,针对 void 类型,我们有以以下的特化版本:
template<> struct my_is_void<void> { static const bool value = true; };
测试代码如下:
my_is_void<bool> t1; cout << t1.value << endl; // 输出0 my_is_void<void> t2; cout << t2.value << endl; // 输出1
当声明 my_is_void<void> t2;
时,使用的是特化版本,故其 value
值为 1。
偏特化(patial spcialization)
偏特化就是只指定一部分而非所有模板参数,或者是参数的一部分而非全部特性。(模板函数只能全特化,没有偏特化)
template<typename t> struct my_is_pointer { static const bool value = false; };
我们对模板参数t进行限制,要求其为一个指针的类型:
template<typename t> struct my_is_pointer<t*> { static const bool value = true; };
测试:
my_is_pointer<int> p1; cout << p1.value << endl; // 输出 0,使用原始模板 my_is_pointer<int*> p2; cout << p2.value << endl; // 输出 1,使偏特化模板,因为指定了 int * 类型的参数
typename
以下模板的声明中, class 和 typename 有什么不同?
template<class t> class test; template<typename t> class test;
答案是没有不同。但除此之外,c++ 并不总是把 class 和 typename 视为等价。有时候我们一定得使用 typename。
默认情况下,c++ 语言假定通过作用域运算符访问的名字不是类型。因此,如果你希望使用一个模板类型参数的类型成员,就必须显式告诉编译器该名字是一个类型。我们通过使用关键字 typename 来实现这一点:
template<typename t> typename t::value_type top(const t &c) { if (!c.empty()) return c.back(); else return typename t::value_type();//必须加typename }
top 函数期待一个容器类型的实参,它使用 typename 指明其返回类型
测试代码:
vector<int> vec; vec.push_back(1); vec.push_back(2); vec.push_back(3); cout << top<vector<int> >(vec) << endl; // 输出3
当我们希望通知编译器一个名字表示类型时,必须使用关键字 typename,而不能使用 class。
traits classes
说完了背景知识,我们正式进入 traits
我们知道,在 stl 中,容器与算法是分开的,彼此独立设计,容器与算法之间通过迭代器联系在一起。那么,算法是如何从迭代器类中萃取出容器元素的类型的?没错,这正是我们要说的 traits classes 的功能。
迭代器所指对象的类型,称为该迭代器的 value_type。我们来简单模拟一个迭代器 traits classes 的实现。
template<class itert> struct my_iterator_traits { typedef typename itert::value_type value_type; };
my_iterator_traits 其实就是个类模板,其中包含一个类型的声明。
对于my_iterator_traits,我们再声明一个偏特化版本。
template<class itert> struct my_iterator_traits<itert*> { typedef itert value_type; //即如果 my_iterator_traits 的实参为指针类型时, //直接使用指针所指元素类型作为 value_type。 };
为了测试 my_iterator_traits 能否正确萃取迭代器元素的类型,我们先编写以下的测试函数。
void fun(int a) { cout << "fun(int) is called" << endl; } void fun(double a) { cout << "fun(double) is called" << endl; } void fun(char a) { cout << "fun(char) is called" << endl; }
我们通过函数重载的方式,来测试元素的类型。
测试代码如下:
my_iterator_traits<vector<int>::iterator>::value_type a; fun(a); // 输出 fun(int) is called my_iterator_traits<vector<double>::iterator>::value_type b; fun(b); // 输出 fun(double) is called my_iterator_traits<char*>::value_type c; fun(c); // 输出 fun(char) is called
为了便于理解,我们这里贴出 vector 迭代器声明代码的简化版本:
template <class t, ...> class vector { public: class iterator { public: typedef t value_type; ... }; ... };
我们来解释 my_iterator_traits<vector<int>::iterator>::value_type a;
语句的含义:
vector<int>::iterator
为 vector<int>
的迭代器,该迭代器包含了 value_type 的声明,由 vector 的代码可以知道该迭代器的 value_type 即为 int 类型。
接着,my_iterator_traits<vector<int>::iterator>
会采用 my_iterator_traits 的通用版本,即 my_iterator_traits<vector<int>::iterator>::value_type
使用 typename itert::value_type
这一类型声明,这里 itert 为 vector<int>::iterator
,故整个语句萃取出来的类型为 int 类型。
对 double 类型的 vector 迭代器的萃取也是类似的过程。
而 my_iterator_traits<char*>::value_type
则使用 my_iterator_traits
的偏特化版本,直接返回 char 类型。
由此看来,通过 my_iterator_traits ,我们正确萃取出了迭代器所指元素的类型。
总结一下我们设计并实现一个 traits class 的过程:
- 确认若干我们希望将来可取得的类型相关信息,例如,对于上面的迭代器,我们希望取得迭代器所指元素的类型;
- 为该信息选择一个名称,例如,上面我们起名为 value_type;
- 提供一个 template 和一组特化版本(例如,我们上面的 my_iterator_traits),内容包含我们希望支持的类型相关信息。