我没答上来,回来查了一下,才发现是和STL泛化编程相关的。从网上找来两篇候捷的大作一读,才有点明白。现在写下来,看我是否真的理解了。首先,我们来了解一下什么是泛化编程。
一般泛型编程时,比如我设计一个算法:
template
I find(I first, I end, T& value)
{
while( first != end && *first != value) //需要重载iterator间的“!= *提领”算子,重载T间的比较算子
first++;//需要重载后置式++算子
return first;
}
first,end是class,一般就是iterator,而class T就是iterator所指之物的类型;在这个模范函数里,我们声明了两个类型I,T。事实上,I与T是相关的,比如int*与int。比如我有一个
struct node
{
int val;
node *pnext;
};
在上面需要运用find算法,就需要一个iterator包装,在这里我申明一个类模板:
template
struct Node{//C++中struct与class的区别在于struct中members默认access level是public,class是private
T *ptr;
Node(const T* p):ptr(p){}
T& operator*() const { return *ptr; }//重载*提领算子,返回的是T类型
T* operator->() const { return ptr; }
Node& operator++{ ptr = ptr->pnext; return *this; }//前置式++,返回的是引用
Node operator++(int) { Node t = *this; ++*this; return t; }//后置式++,因ptr已经改变,返回的不是引用
bool operator==(const Node& i){ return i.ptr == ptr; }
bool operator!= (const Node& i){ return i.ptr != ptr; }//同样为了find函数中而重载!=符号
};
同样,我们在*first != value之间,我们需要重载!=算子(在find函数中是*first与value比较,而*first是T类型,这里T类型就是struct node类型):
bool operator==(const node& i, int value){ return i.value == value; }
bool operator!= (const node& i, int value){ return i.value != value; }
好了,现在我们可以使用以下代码使用我们的链表: 到这里,我们学会了如何封装一个struct,使其能被find函数调用,很有成就感吧?感谢jjh吧。 我们重新审视find函数,发现find函数需要声明两个类型,一个是T,一个是I,其实T就是的*I,C++没有typeof算子,但是编译器有推导功能: 办法一: 方法二(嵌套类型声明,原文称“巢狀式的型別宣告”): 那泛化函数可以如此声明: 然后我们可以用下面的代码: template 于是乎,func函数可以写成如下: //测试 Traits技术可以用来获得一个 类型 的相关信息的。 首先假如有以下一个泛型的迭代器类,其中类型参数 T 为迭代器所指向的类型: 《STL源码解析》是侯杰大师翻译的著作,其中在Iterator一章着重介绍了traits技巧,认为traits技巧是搞懂的STL源码的入门钥匙,既然编写STL的神人们都这么重视traits,那么traits到底能帮助我们解决什么问题呢?traits的作用在于能“提取”出类型的特性。 举个例子:有个需求是这样的,需要写一个全局的print函数,来打印入参的对象,假设用OO的思想:设计一个cprint的基类,在此基类中用虚函数print,然后每个类型都继承cprint基类,并重写print,那么全局函数就可以这么写: void print(const cprint& _p) { _p.print(); } 很完美吧?哈哈,自己都看着得意,那么问题来了我需要print的类型是原生指针怎么办呢,OO的思想可以实现,但是需要写个包装类,试着换个角度看这个问题吧,来用traits技巧来解决看看: 首先声明两个结构体,没有任何东西,只为了标志,我们的程序世界就靠它们来为我们区分谁有print,谁木有print了。 struct _type_true {}; struct _type_false {}; //接下来这个是一个测试类,其有print函数。 struct student { unsigned intid; unsigned intage; char name[128]; void print(void) const { std::cout << "id:"<< id << "\t"<< "age:" << age << "\t"<< "name:" << name << std::endl; }; }; // OK,下面最厉害的traits就要出场了,默认的用_type_true来标记,再用偏特化(partial specialization)的方法声明原生指针是_type_false,木有print的 template struct print_traits { typedef _type_truehas_print; };
template<> struct print_traits { typedef _type_falsehas_print; };
//接下来就是全局的print函数了:通过print_traits的提取,区别调用_print(T *_p, _type_true)和_print(T *_p, _type_false)。 template void print(T _P) { typedef typenameprint_traits _print(_P,has_print()); }
template inline void _print(T _P, _type_true) { _P.print(); }
template inline void _print(T _P, _type_false) { std::cout<< "我是没有Print的,最后尝试下<<操作符吧:" << _P << std::endl; } //测试下哈 int main(int argc,char* argv[]) { student s1; s1.id= 0; s1.age= 19; strncpy(s1.name,"xia kan",sizeof(s1.name)); print(s1); int i = 0; print(i); return 0; } 总结下traits的运用方法: 声明标记-》运用标记,区分类别(traits)-》设计接口函数和不同类型的重载函数-》利用编译器的调用判定来决定调用哪个函数 traits是编译期多态的一个好应用~!从网上摘了这段话,很能说明问题: “ traits技巧对类型做了什么?有什么作用?类型和类型的特性本是耦合在一起,通过traits技巧就可以将两者解耦。从某种意思上说traits方法也是对类型的特性做了泛化的工作,通过traits提供的类型特性是泛化的类型特性。从算法使用traits角度看,使用某一泛型类型的算法不必关注具体的类型特性(关注的是泛化的类型特性,即通过traits提供的类型特性)就可以做出正确的算法过程操作;从可扩展角度看,增加或修改新的类型不影响其它的代码,但如果是在type_traits类中增加或修改类型特性对其它代码有极大的影响;从效率方法看,使用type_traits的算法多态性选择是在编译时决定的,比起在运行时决定效率更好。 ”
node *head,*end;
node *tmp = new node;
tmp->value = 100;
tmp->pnext = NULL;
head = end = tmp;
for(int i = 0; i < 10; ++i)
{
tmp = new node;
tmp->value = i+1;
tmp->pnext = NULL;
end->pnext = tmp;
end = tmp;
}
//以上代码生成了一个链表,现在看怎么运用我们的find函数:
Node
r = find(Node
//Node
//同理,Node
if( NULL != r ) cout<<(*r).value<
template
void fun_impl(I i, T v)
{
//do some work
}
template
void fun(I i)
{
fun_impl(i, *i);//编译器通过*i推导出*i的类型,然后调用fun_impl完成功能
}
于是我们可以通过如下代码完成功能:
int i;
fun(&i);
似乎解决了问题,但是问题不断,如果入参不是一般参数,而是一个函数的传回值,就不灵了。
假设我们的Node模板类封装了类型为T节点
template
struct Node{
typedef T value_type;//嵌套类型
T *ptr;
Node(const T* p):ptr(p){}
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
.....
};
template
typename Node
func(Node
{
return *(it.ptr);
}
Node
cout<
这个函数的问题在于每个需要为每一种iterator写一个func,Node写一个,以后Stack也许也要写一个,有没有办法可以避免具体类型Node之类出现呢?当然有了(traits粉墨登场),traits是特性的意思,从众多iterator中“提取”type特性:
struct iterator_traits
{
typedef typename Iterator::value_type value_type;//typename为了使编译通过,其实g++ 3.4.2下不会报错
};
至于原生指针,我们使用partial specialization
template
struct iterator_traits
{
typedef T value_type;
}
template
struct iterator_traits
{
typedef T value_type;
}
template
typename iterator_traits
func(T t)
{
return *(t.value);
}
int main( char argc, char *argv[] )
{
char *p[100];
Node
std::cout<
Node
std::cout<
Node
return 0;
template <typename T>
class myIterator
{
...
};
当我们使用myIterator时,怎样才能获知它所指向的元素的类型呢?我们可以为这个类加入一个内嵌类型,像这样:
template <typename T>
class myIterator
{
typedef T value_type;
...
};
这样当我们使用myIterator类型时,可以通过 myIterator::value_type来获得相应的myIterator所指向的类型。
现在我们来设计一个算法,使用这个信息。
template
typename myIterator
{
...
}
这里我们定义了一个函数Foo,它的返回为为 参数i 所指向的类型,也就是T,那么我们为什么还要兴师动众的使用那个value_type呢? 那是因为,当我们希望修改Foo函数,使它能够适应所有类型的迭代器时,我们可以这样写:
template
typename I::value_type Foo(I i)
{
...
}
现在,任意定义了 value_type内嵌类型的迭代器都可以做为Foo的参数了,并且Foo的返回值的类型将与相应迭代器所指的元素的类型一致。至此一切问题似乎都已解决,我们并没有使用任何特殊的技术。然而当考虑到以下情况时,新的问题便显现出来了:
原生指针也完全可以做为迭代器来使用,然而我们显然没有办法为原生指针添加一个value_type的内嵌类型,如此一来我们的Foo()函数就不能适用原生指针了,这不能不说是一大缺憾。那么有什么办法可以解决这个问题呢? 此时便是我们的主角:类型信息榨取机 Traits 登场的时候了
....drum roll......
我们可以不直接使用myIterator的value_type,而是通过另一个类来把这个信息提取出来:
template <typename T>
class Traits
{
typedef typename T::value_type value_type;
};
这样,我们可以通过 Traits
template
typename Traits::value_type Foo(I i)
{
...
}
然而,即使这样,那个原生指针的问题仍然没有解决,因为Trait类一样没办法获得原生指针的相关信息。于是我们祭出C++的又一件利器--偏特化(partial specialization):
template <typename T>
class Traits
{
typedef typename T value_type;
};
通过上面这个 Traits的偏特化版本,我们陈述了这样一个事实:一个 T* 类型的指针所指向的元素的类型为 T。
如此一来,我们的 Foo函数就完全可以适用于原生指针了。比如:
int * p;
....
int i = Foo(p);
Traits会自动推导出 p 所指元素的类型为 int,从而Foo正确返回。