什么是traits,为什么人们把它认为是C++ Generic Programming的重要技术?
简洁地说,traits如此重要,是因为此项技术允许系统在编译时根据类型作一些决断,就好像在运行时根据值来做出决断一样。更进一步,此技术遵循“另增一个间接层”的谚语,解决了不少软件工程问题,traits使您能根据其产生的背景(context) 来做出抉择。这样最终的代码就变得清晰易读,容易维护。如果你正确运用了traits技术,你就能在不付出任何性能和安全代价的同时得到这些好处,或者能够契合其他解决方案上的需求。
先举个浅显易懂的例子来说明traits的用法:
//首先假如有以下一个泛型的迭代器类,其中类型参数 T 为迭代器所指向的类型:
template <typename T>
class myIterator
{
};
那么当使用myIterator时,怎样才能知道它所指向元素的类型呢?一种解决方案是为这个类加入一个内嵌类型:
template <typename T>
class myIterator
{
typedef T value_type;
};
当使用myIterator时,可以通过myIterator::value_type来获得相应的myIterator所指向的类型。下面举例使用:
template <typename T>
typename myIterator<T>::value_type func(myIterator<T> i)
{
}
这里定义了一个函数func,返回值类型为参数i所指的类型,也就是模板参数T,那么为什么不直接使用模板参数T,而要绕着圈去使用那个value_type呢?所以我们返回来,当修改func函数时,它能够适应所有类型的迭代器,不是更好吗?如下所示:
template <typename I>
//这里的I可以是任意类型的迭代器
typename I::value_type func(I i)
{
}
现在,任意定义了value_type内嵌类型的迭代器都可以做为func的参数了,并且func的返回值的类型将与相应迭代器所指的元素的类型一致。至此一切问题似乎都已解决,并且似乎并没有使用任何特殊的技术。
然而当考虑到以下情况时,新的问题便显现出来了:原生指针也完全可以做为迭代器来使用,然而显然没有办法为原生指针添加一个value_type的内嵌类型,如此一来func()函数就不能适用原生指针了,这不能不说是一大缺憾。那么有什么办法可以解决这个问题呢?此时不禁想到了用Traits萃取类型信息。可以不直接使用myIterator的value_type,而是通过Traits类来把这个信息提取出来:(不同的类型,可以有不同的提取方式)
template <typename T>
class Traits
{
typedef typename T::value_type value_type;
};
这样以后就可以通过Traits<myIterator>::value_type来提取出myIterator中的value_type,于是func函数改写成:
template <typename I>
//这里的I可以是任意类型的迭代器
typename Traits<I>::value_type Foo(I i)
{
}
然而,即使这样,那个原生指针的问题仍然没有解决,因为Trait类还是没办法获得原生指针的相关信息。于是不妨将Traits偏特化(partial specialization):(通过特化、重载特化等手段产出不同的提取方式)
template <typename T>
class Traits<T*> //注意 这里针对原生指针进行了偏特化
{
typedef typename T value_type;
};
通过上面这个Traits的偏特化版本,一个T*类型的指针所指向的元素的类型为T。如此一来,我们func函数就完全可以适用于原生指针了。比如:
int * p;
.
int i = func(p);
Traits会自动推导出p所指元素的类型为int,从而func正确返回。
-----------------------------------------------------------------------------------------------------------------------------------------------------------
现在再看一个更加一般的例子——smart pointers。(智能指针)
假设你正在设计一个SmartPtr模板类,对于一个smart pointer 来说,它的最大的用处是可以自动管理内存问题,同时在其他方面又像一个常规指针。但是有些C++的Smart pointer实现技术却非常令人难以理解。这一残酷的事实带来了一个重要实践经验:你最好尽一切可能一劳永逸,写出一个出色的、具有工业强度的 smart pointer来满足你所有的需求。此外,你通常不能修改一个类来适应你的smart pointer,所以你的SmartPtr一定要足够灵活。
有不少类层次使用引用计数(reference counting)以及相应的函数管理对象的生存期。然而,并没有reference counting的标准实现方法,每一个C++库的供应商在实现的语法和/或语义上都有所不同。例如,在你的应用程序中有这样两个interfaces:
第一种智能指针--大部分的类实现了RefCounted接口:
class RefCounted
{
public:
virtual void IncRef() = 0;
virtual bool DecRef() = 0;
// if you DecRef() to zero references, the object is destroyed
// automatically and DecRef() returns true
virtual ~RefCounted() {}
};
第二种智能指针--第三方提供的Widget类使用不同的接口:
class Widget
{
public:
void AddReference();
int RemoveReference();
// returns the remaining number of references; it's the client's
// responsibility to destroy the object
};
不过你并不想维护两个smart pointer类,你想让两种类共享一个SmartPtr。一个基于traits的解决方案把两种不同的接口用语法和语义上统一的接口包装起来,建立针对普通类的通用模板,而针对Widget建立一个特殊化版本,如下:
template <class T>
class RefCountingTraits
{
static void Refer(T* p)
{
p->IncRef(); // assume RefCounted interface
}
static void Unrefer(T* p)
{
p->DecRef(); //assume RefCounted interface
}
};
template<>
class RefCountingTraits<Widget>
{
static void Refer(Widget* p)
{
p->AddReference(); //use Widget interface
}
static void Unrefer(Widget* p)
{
//use Widget interface
If (p->RemoveReference() == 0)
delete p;
}
};
在SmartPtr里,我们像这样使用RefCountingTraits:
template <
class T>
class SmartPtr
{
private:
typedef RefCountingTraits<T> RCTraits;
T* pointee_;
public:
~SmartPtr()
{
RCTraits::Unrefer(pointee_);
}
};
当然在上面的例子里,你可能会争论说你可以直接特殊化Widget类的SmartPtr的构造与析构函数。你可以使用把模板特殊化技术用在 SmartPtr本身,而不是用在traits上头,这样还可以消除额外的类。尽管对这个问题来说这种想法没错,但还是由一些你需要注意的缺陷:
- 这么干缺乏可扩展性。如果给SmartPtr再增加一个模板参数,你不能特殊化这样一个SmartPtr<T. U>,其中模板参数T是Widget,而U可以为其他任何类型。
- 最终代码不那么清晰。Trait有一个名字,而且把相关的东西很好的组织起来,因此使用traits的代码更加容易理解。相比之下,用直接特殊化SmartPtr成员函数的代码,看上去更招黑客的喜欢。
用继承机制的解决方案,就算本身完美无瑕,也至少存在上述的缺陷。解决这样一个变体问题,使用继承实在是太笨重了。此外,通常用以取代继承方案的另一种经典机制——containment,用在这里也显得画蛇添足,繁琐不堪。相反,traits方案干净利落,简明有效,物合其用,恰到好处。
Traits的一个重要的应用是“interface glue”(接口胶合剂),通用的、可适应性极强的适配子。如果不同的类对于一个给定的概念有着不同的实现,traits可以把这些实现再组织统一成一个公共的接口。对于一个给定类型提供多种TRAITS:现在,我们假设所有的人都很喜欢你的SmartPtr模板类,直到有一天,在你的多线程应用程序里开始现了神秘的bug。你发现罪魁祸首是Widget,它的引用计数函数并不是线程安全的。现在你不得不亲自实现Widget:: AddReference和Widget::RemoveReference,最合理的位置应该是在RefCountingTraits中,打上个补丁吧:
// Example 7: Patching Widget's traits for thread safety
template <>
class RefCountingTraits<Widget>
{
static void Refer(Widget* p)
{
Sentry s(lock_); // serialize access
p->AddReference();
}
static void Unrefer(Widget* p)
{
Sentry s(lock_); // serialize access
if (p->RemoveReference() == 0)
delete p;
}
private:
static Lock lock_;
};
不幸的是,虽然你重新编译、测试之后正确运行,但是程序慢得像蜗牛。仔细分析之后发现,你刚才的所作所为往程序里塞了一个糟糕的瓶颈。实际上只有少数几个Widget是需要能够被好几个线程访问的,余下的绝大多数Widget都是只被一个线程访问的。你要做的是告诉编译器按你的需求分别使用多线程traits和单线程traits这两个不同版本。你的代码主要使用单线程traits。
如何告诉编译器使用哪个traits?一种方法是把traits作为另一个模板参数传给SmartPtr。缺省情况下传递老式的traits模板,而用特定的类型实例化特定的模板。
template <
class T,
class RCTraits = RefCountingTraits<T> >
class SmartPtr
{
};
你对单线程版的RefCountingTraits<Widget>不做改动,而把多线程版放在一个单独的类中:
class MtRefCountingTraits
{
static void Refer(Widget* p)
{
Sentry s(lock_); // serialize access
p->AddReference();
}
static void Unrefer(Widget* p)
{
Sentry s(lock_); // serialize access
if (p->RemoveReference() == 0)
delete p;
}
private:
static Lock lock_;
};
现在你可将SmartPtr<Widget>用于单线程目的,将SmartPtr<Widget,MtRefCountingTraits>用于多线程目的。
最后,以SGI STL中的__type_traits结束本篇讨论,在SGI 实现版的STL中,为了获取高效率,提供了__type_traits,用来提取类的信息,比如类是否拥有trival的构造、析构、拷贝、赋值操作,然后跟据具体的信息,就可提供最有效率的操作。以下摘录cygwin的gcc3.3源码,有改动,在<type_traits.h>中。
struct __true_type {};
struct __false_type {};
template <class _Tp>
struct __type_traits
{
typedef __true_type this_dummy_member_must_be_first;
typedef __false_type has_trivial_default_constructor;
typedef __false_type has_trivial_copy_constructor;
typedef __false_type has_trivial_assignment_operator;
typedef __false_type has_trivial_destructor;
typedef __false_type is_POD_type;
};
对于普通类来讲,为了安全起见,都认为它们拥有non-trival的构造、析构、拷贝、赋值函数,POD是指plain old data。接下来对C++的原生类型(bool,int, double之类)定义了显式的特化实现,以double为例:
template<>
struct __type_traits<long double> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
还有,对所有的原生指针来讲,它们的构造、析构等操作也是trival的,因此有:
template <class _Tp>
struct __type_traits<_Tp*> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
简化<stl_algobase.h>中copy的部分代码来说明对__type_traits的应用。
template<typename _Tp>
inline _Tp* __copy_trivial(const _Tp* __first, const _Tp* __last, _Tp* __result)
{
memmove(__result, __first, sizeof(_Tp) * (__last - __first));
return __result + (__last - __first);
}
template<typename _Tp>
inline _Tp* __copy_aux (_Tp* __first, _Tp* __last, _Tp* __result, __true_type)
{ return __copy_trivial(__first, __last, __result); }
template<typename _Tp>
inline _Tp* __copy_aux (_Tp* __first, _Tp* __last, _Tp* __result, __false_type)
{ 另外处理;}
template<typename _InputIter, typename _OutputIter> inline
_OutputIter copy (_InputIter __first, _InputIter __last, _OutputIter __result)
{
typedef typename iterator_traits<_InputIter>::value_type _ValueType;
typedef typename __type_traits<_ValueType>::has_trivial_assignment_operator _Trivial;
return __copy_aux(__first, __last, __result, _Trivial());
}
Copy 函数利用__type_traits判断当前的value_type是否有trival的赋值操作,如果是,则产生类__true_type的实例,编译 时选择__copy_trivial函数进行memmove,效率最高。如果是non-trival的赋值操作,则另作处理,效率自然低些。__true_type和__false_type之所以是类,就因为C++的函数重载是根据类型信息来的,不能依据参数值来判别。使用SGI STL时,可以为自己的类定义__type_traits显式特化版本,以求达到高效率。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/budTang/archive/2008/05/06/2397013.aspx