Traits 技术

<span style="font-family: 'Hiragino Sans GB W3', 'Hiragino Sans GB', Arial, Helvetica, simsun, u5b8bu4f53; background-color: rgb(255, 255, 255);">什么是traits,为什么人们把它认为是C++ Generic Programming的重要技术?</span>
简洁地说,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,而是通过另一个类来把这个信息提取出来:
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显式特化版本,以求达到高效率。

再加一个例子

#include <iostream>  
  
namespace namespace269 {  
      
    /** 2两个空白类,没有任何成员,不会带来额外负担,却能通过类型代表真假,可以用于函数特化参数 */  
    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;  
    };  
}  
  
template <class T>  
void test_tpye_traits(T& t, namespace269::__true_type) {  
    std::cout << "true" << std::endl;  
}  
  
template <class T>  
void test_tpye_traits(T& t, namespace269::__false_type) {  
    std::cout << "false" << std::endl;  
}  
  
template <typename T>  
void test_tpye_traits(T& t) {  
    typedef typename namespace269::__type_traits<T>::is_POD_type is_POD_type;      
    test_tpye_traits(t, typename namespace269::__type_traits<T>::is_POD_type());  
}  
  
  
class A {  
public:  
};  
  
class B {  
};  
  
  
namespace namespace269 {  
    template <>  //模板的特化,参照下一篇文章。
    struct __type_traits<A> {  
        typedef __true_type is_POD_type;  
    };  
  
    template <>  
    struct __type_traits<B> {  
        typedef __false_type is_POD_type;  
    };  
}  
  
int main(void) {  
    A a;  
    B b;  
    test_tpye_traits(a);   // true  
    test_tpye_traits(b);   // false  
  
    return 0;  
}  




转自:http://blog.csdn.net/budTang/archive/2008/05/06/2397013.aspx

http://silencethinking.blog.163.com/blog/static/91149056201082112516808/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值