涛 的个人资料徒然草的新园子照片日志列表更多 工具 帮助
2009/3/6

boost库中的traits学习笔记

boost中的traits分类:
1. Primary Type Categorisation(初级类型分类)
2. Secondary Type Categorisation(次级类型分类)
3. Type Properties(类型属性)
4. Relationships Between Types(类型间关系)
5. Transformations Between Types(类型间转换)
6. Synthesizing Types(类型合成)
7. Function Traits(函数traits)

本人尝试直接阅读相应源码,但因为太多宏、模板等目前来说还过于晦涩的语法、技巧的堆积,只好抄袭别人的笔记了。

  • 初级类型分类
    is_array (boost/type_traits/is_array.hpp)
    // 缺省
    template<typename T>
    struct is_array{
    static const bool value=false;
    };
    // 偏特化
    template<typename T,size_t N>
    struct is_array<T[N]>{
    static const bool value=true;
    };


    C++标准允许整型常量表达式作为模板参数,上面的N就是这样。这也说明出现在模板偏特化版本中的模板参数(在本例中为typename T,size_t N两个)个数不一定要跟缺省的(本例中为typename T一个)相同,但出现在类名称后面的参数个数却要跟缺省的个数相同(is_array<T[N]>,T[N]为一个参数,与缺省的个数相同)。

    使用
    is_array<int [10]>::value // true(T=int,N=10)
    is_array<int>::value // false(T=int)

    is_class(.../is_class.hpp)
    定义
    template <typename T>
    struct is_class_impl
    {
           template <class U>
           static ::yes_type is_class_tester(void(U::*)(void));

           template <class U> static ::no_type is_class_tester(...);

           // ice_and是一个元函数,提供逻辑与(AND)操作
           static const bool value = ::ice_and<
                 sizeof(is_class_tester<T>(0))==sizeof(::yes_type), // #3
                 ::ice_not<::is_union<T>::value >::value
                 >::value
    };
    template<typename T>
    struct is_class{
           // 所有实现都在is_class_imp中
           static const bool value = is_class_impl<T>::value;
    };
    注解
    ::boost::type_traits::yes_type是一个typedef:
    typedef char yes_type;
    因此sizeof(yes_type)为1.

    ::boost::type_traits::no_type则是一个struct:
    struct no_type{
           char padding[8];
    };
    因此sizeof(no_type)为8。
    这两个类型一般被用作重载函数的返回值类型,这样通过检查返回值类型的大小就知道到底调用了哪个函数,它们的定义位于“boost/type_traits/detail/yes_no_type.hpp”中。
    is_class_impl中有两个static函数,第一个函数仅当模板参数U是类时才能够被实例化,因为它的参数类型是void(U::*)(void),即指向成员函数的指针。第二个函数具有不定量任意参数列表,C++标准说只有当其它所有的重载版本都不能匹配时,具有任意参数列表(...)的重载版本才会被匹配。所以,如果T为类,则void (T::*)(void)这种类型就存在,所以对is_class_tester<T>(0)的重载决议将是调用第一个函数,因为将0赋给任意类型的指针都是合法的。而如果T不是类,则就不存在void(T::*)(void)这种指针类型,所以第一个函数就不能实例化,这样,对is_class_tester<T>(0)的重载决议结果只能调用第二个函数。
    现在注意#3处的表达式:
    sizeof(is_class_tester<T>(0))==sizeof(...::yes_type) // #3
    按照上面的推导,如果T为类,is_class_tester<T>(0)实际调用第一个重载版本,返回yes_type,则该表达式求值为true。如果T不是类,则is_class_tester<T>(0)调用第二个重载版本,返回no_type,则该表达式求值为false。这正是我们想要的。

    一个值得注意的地方是:在sizeof的世界里,没有表达式被真正求值,编译器只推导出表达式的结果的类型,然后给出该类型的大小
    比如,对于sizeof(is_class_tester<T>(0))编译器实际并不调用函数的代码来求值,而只关心函数的返回值类型。所以声明该函数就够了。另一个值得注意之处是is_class_tester的两个重载版本都用了模板函数的形式。第一个版本用模板形式的原因是如果不那样做,而是这样
    static yes_type is_class_tester(void(T::*)(void));
    的话,则当T不是类时,该traits将不能通过编译,原因很简单,当T不是类时void (T::*)(void)根本不存在。然而,使用模板时,当T不是类时该重载版本会因不能实例化而根本不编译,C++标准允许不被使用的模板不编译(实例化)。这样编译器就只能使用第二个版本,这正合我们的意思。
    而is_class_tester的第二个重载版本为模板则是因为第一个版本是模板,因为在#3处对is_class_tester的调用是这样的:
    is_class_tester<T>(0)
    如果第二版本不是模板的话,这样调用只能解析为对is_class_tester模板函数(即第一个版本)的调用,于是重载解析也就不复存在了。“等等!”你意识到了一些问题:“模板函数的调用可以不用显式指定模板参数!”好吧,也就是说你试图这样写:
    // 模板
    template <class U>
    static ...::yes_type is_class_tester(void(U::*)(void));
    // 非模板
    static ...::no_type is_class_tester(...);
    然后在#3标记的那一行这样调用:
    is_class_tester(0) // 原来是is_class_tester<T>(0))
    是的,我得承认,这的确构成了函数重载的条件,也的确令人欣喜的通过了编译,然而结果肯定不是你想要的。你会发现对所有类型T,is_class<T>::value现在都是0了!
    也就是说,编译器总是调用is_class_tester(..);这是因为,当调用的函数的所有重载版本中有一个或多个为模板时,编译器首先要尝试进行模板函数实例化而非重载决议,而在尝试实例化的过程中,编译器会进行模板参数推导,0的类型被编译器推导为int(0虽然可以赋给指针,但0的类型不可能被推导为指针类型,因为指针类型可能有无数种,而事实上C++是强类型语言,对象只能属于某一种类型),而第一个函数的参数类型void (U::*)(void)根本无法与int匹配(因为如果匹配了,那么模板参数U被推导为什么呢?)。所以第一个版本实例化失败后编译器只能采用非模板的第二个版本。结果如你所见,是令人懊恼的。然而如果你写的是is_class_tester<T>(0)你其实是显式实例化了is_class_tester每一个模板函数(除了那些不能以T为模板参数实例化的),而它们都被列入接受重载决议的侯选单,然后编译器要做的就只剩下重载决议了。(关于编译器在含有模板函数的重载版本时是如何进行重载决议的,可参见C++ Primer的Function Templates一节,里面有极其详细的介绍)。
    初级类型分类还有:
    is_void is_integral is_float is_pointer is_reference is_union is_enum is_function
  • 次级类型分类
    is_member_function_pointer(.../is_member_function_pointer.hpp)
    定义(.../detail/is_mem_fun_pointer_impl.hpp)

    // 缺省版本
    template <typename T>
    struct is_mem_fun_pointer_impl{
           static const bool value = false;
    };
    // 偏特化版本,匹配无参数的成员函数
    template <class R, class T >
    struct is_mem_fun_pointer_impl<R (T::*)() >{
           static const bool value = true;
    };
    //匹配一个参数的成员函数
    template <class R, class T , class T0>
    struct is_mem_fun_pointer_impl<R (T::*)( T0) >{
           static const bool value = true;
    };
    // 其它版本只是匹配不同参数个数的成员函数的偏特化而已,参见源文件。
    template<class T>
    struct is_mem_function_pointer{
           static const bool value = is_mem_fun_pointer_impl<T>::value;
    };

    注解
    假设你有一个类X,你这样判断:
    is_mem_function_pointer<int (X::*)(int)>::value
    则编译器会先将is_mem_function_pointer的模板参数class T推导为int (X::*)(int),然后将其传给is_mem_fun_pointer_impl,随后编译器寻找后者的偏特化版本中最佳匹配项为:is_mem_fun_pointer_impl<R(T::*)(T0)>
    其中R=int,T=X,T0=int。而该偏特化版本的::value=true。
    次级类型分类还有:
    is_arithmetic is_fundamental is_object is_scalar is_compound

  • 类型属性
    is_empty(.../is_empty.hpp)

    定义
    // 如果T是空类,那么派生类的大小就是派生部分的大小即sizeof(int)*256
    template <typename T>
    struct empty_helper_t1 : public T{
            empty_helper_t1();
            int i[256];
    };
    struct empty_helper_t2{
            int i[256];
    }; // 大小为sizeof(int)*256

    通过比较以上两个类的大小可以判断T是否为空类,如果它们大小相等则T为空类。反之则不为空。

    这里一个值得注意的地方是:若定义一个空类E,则sizeof(E)为1(这一个字节是用于在内存中唯一标识该类的不同对象。如果sizeof(E)为0,则意味着不同的对象在内存中的位置没有区别,这显然有违直观)。然而如果有另一个非空类继承自E,那么这一个字节的内存就不需要。也就是说派生类的大小等于派生部分的大小,而非加上一个字节。

    // 这个辅助类的作用是:如果T不是类则使用该缺省版本如果T是类则使用下面的偏特化版本。而判断T是否为类的工作则由上面讲过的is_class<>traits来做。

    template <typename T, bool is_a_class = false>
    struct empty_helper {
    static const bool value = false;
    };

    template <typename T>
    struct empty_helper<T, true> // #5
    {
    static const bool value = (sizeof(empty_helper_t1<T>) == sizeof(empty_helper_t2));
    };

    template <typename T>
    struct is_empty_impl{
    // remove_cv将T的const volatile属性去掉,这是因为在作为基类的类型不能有const/volatile修饰。
    typedef typename remove_cv<T>::type cvt;
    static const bool value = ice_or<
         empty_helper<cvt, is_class<T>::value>::value, // #4
         BOOST_IS_EMPTY(cvt)
         >::value;
    };

    注解
    在#4处,如果is_class<T>::value为true(即T为类)则empty_helper<cvt,is_class<T>::value>::value实际决议为empty_helper<cvt,true>,这将采用偏特化版本#5,则结论出现。否则T不是类,则采用缺省版本,结果::value为false。

    is_polymorphic(.../is_polymorphic.hpp)

    is_plymorphic的运作机制基于一个基本事实:一个多态的类里面会有一个虚函数表指针(一般称为vptr),它指向一个虚函数表(一般称为vtbl)。后者保存着一系列指向虚函数的函数指针以及运行时类型识别信息。一个虚函数表指针通常占用4个字节(32寻址环境下的所有指针都占用4个字节)。反之,如果该类不是多态,则没有这个指针的开销。基于这个原理,我们可以断定:如果类X不是多态类(没有vtbl及vptr),则如果从它派生一个类Y,Y中仅含有一个虚函数,这会导致sizeof(Y)>sizeof(X)(这是因为虚函数的首次出现导致编译器必须在Y中加入vptr的缘故)。反之,如果X原本就是多态类,则sizeof(Y)==sizeof(X)(因为这种情况下,Y中其实已经有了从X继承而来的vtbl及vptr,编译器所要做的只是将新增的虚函数纳入到vtbl中去)。

    定义
    // 当T为类时使用这个版本
    template <class T>
    struct is_polymorphic_imp1 {

    typedef typename remove_cv<T>::type ncvT;
    // ncvT是将T的const volatile修饰符去掉后的类型,因为public后不能跟这样的修饰符,该类里没有虚函数
    struct d1 : public ncvT {
    d1();
    ~d1() // throw();
    char padding[256];
    };

    struct d2 : public ncvT // 在d2中加入一个虚函数{
    d2();
    //加入一个虚函数,如果ncvT为非多态则会导致vptr的加入从而多占用4字节
    virtual ~d2() // throw();
    char padding[256];
    };

    // 如果T为多态类则value为true
    static const bool value = (sizeof(d2) == sizeof(d1));

    };

    // 当T并非类时采用这个版本
    template <class T>
    struct is_polymorphic_imp2{
    // 既然T不是类,那么就不存在多态,所以总是false
    static const bool value = false;
    };

    // 这个selector根据is_class的真假来选择判断的方式
    template <bool is_class>
    struct is_polymorphic_selector{
         // 如果is_class为false则由is_polymorphic_imp2来判断,这将导致结果总是false
         template <class T>
         struct rebind {
            typedef is_polymorphic_imp2<T> type; // 使用_imp2
        };
    };

    //当is_class为true时使用该特化版本
    template <>
    struct is_polymorphic_selector<true> // #7
    {
         // 如果is_class为true,则由is_polymorphic_imp1<>来作判断
         template <class T>
         struct rebind{
               typedef is_polymorphic_imp1<T> type; // 使用_imp1
         };
    };

    // is_polymorphic完全由它实现
    template <class T>
    struct is_polymorphic_imp{

    // 选择selector
            typedef is_polymorphic_selector<is_class<T>::value> selector; // #6
            typedef typename selector::template rebind<T> binder; // #8
            typedef typename binder::type imp_type; // #9
            static const bool value = imp_type::value;
    };

    注解
    #6处如果T为类,则is_class<T>::value为true,则那一行实际上就是:
    typedef is_polymorphic_selector<true> selector;
    这将决议为is_polymorphic_selector的第二个重载版本#7,其中的template rebind将判断的任务交给is_polymorphic_imp1,所以#8行的binder其实就是is_polymorphic_selector<true>::rebind<T>。而#9行的imp_type其实就是is_polymorphic_imp1<T>,结果正如预期。如果T不是类,按照类似的推导过程,最终会推导至is_polymorphic_imp2<T>::value,这正是false。

    “嗨!这太烦琐了!”你抱怨道:“可以简化!”。我知道,你可能会想到使用boost::ct_if(ct_if是?:三元操作符的编译期版本,像这样使用:

    typedef
    ct_if<CompileTimeBool,TypeIfTrue,TypeIfFalse>::value result;

    则当CompileTimeBool为true时result为TypeIfTrue,否则result为TypeIfFalse。ct_if<>的实现很简单,模板偏特化而已)。于是你这样写:

    typedef typename boost::ct_if<
    is_class<T>::value,
    is_polymorphic_imp1<T>,
    is_polymorphic_imp2<T>,
    >::type imp_type;

    static const bool value = imp_type::value;

    这在我的VC7.0环境下的确编译通过并正常工作,但是有一个小问题:假如T不是class,比如,T是一个int,则编译器的类型推导会将is_polymorphic_imp1<int>赋给ct_if的第二个模板参数,在这个过程中编译器会不会实例化is_polymorphic_imp1<int>(或者,换句话说,编译器会不会去查看它的定义)呢?如果实例化了,那么其内部的struct d1 : public ncvT会不会也跟着实例化为struct d1:public int,如果是这样,那么将会有编译期错误,因为C++标准不允许有public int这样的东西出现。事实上我的编译器没有报错,即是说它并没有去查看is_polymorphic_imp1<int>的定义。

    而C++标准实际上也支持这种做法。但boost库中的做法更为保险,也许是为了应付一些老旧的编译器。
    类型属性traits还有:alignment_of is_const is_volatile is_pod has_trivial_constructor等

  • 类型间关系

    is_base_and_derived(boost/type_traits/is_base_and_derived.hpp)

    定义
    template<typename B, typename D>
    struct bd_helper{
    template<typename T>
    static type_traits::yes_type check(D const volatile *, T);
    static type_traits::no_type check(B const volatile *, int);
    };

    template<typename B, typename D>
    struct is_base_and_derived_impl2{
    struct Host{
    // 该转换操作符当对象为const对象时才起作用
    operator B const volatile *() const;
    operator D const volatile *();
    };

    static const bool value = sizeof(bd_helper<B,D>::check(Host(), 0)) // #10
    == sizeof(type_traits::yes_type);

    };

    以上就是is_base_and_derived的底层机制。下面我就为你讲解它所仰赖的机制,假设有这样的类继承体系:
    struct B {};
    struct B1 : B {};
    struct B2 : B {};
    struct D : private B1, private B2 {};

    将D*转换为B1*会导致访问违规,因为私有基类部分无法访问,但是后面解释了这为什么不会发生。

    首先来看一些术语:
    SC - Standard Conversion
    UDC - User-Defined Conversion
    一个user-defined转换序列由一个SC后跟一个UDC后再跟一个SC组成。其中头尾两个SC都可以为到自身的转换(如:D->D),#10处将一个缺省构造的Host()交给bd_helper<B,D>::check函数。对于static no_type check(B const volatile *, int),我们有如下可行的隐式转换序列:

    Host -> Host const -> B const volatile* (UDC)

    Host -> D const volatile* (UDC) -> B1 const volatile* / B2 const volatile* -> B const volatile* (SC)

    而对于static yes_type check(D const volatile *, T),我们则有如下转换序列:
    Host -> D const volatile* (UDC)

    C++标准说,在重载决议中选择最佳匹配函数时,只考虑标准转换(SC)序列,而这个序列直到遇到一个UDC为止,对于第一个函数,将Host -> Host const与Host -> Host比较,显然选择后者。因为后者是前者的一个真子集。因此,去掉第一个转换序列我们得到:

    C -> D const volatile* (UDC) -> B1 const volatile* / B2 const volatile* -> B const volatile* (SC)
    vs.
    C -> D const volatile* (UDC)

    这里采用选择最短序列的原则,选择后者,这表明编译器甚至根本不需要去考虑向B转换的多重路径,或者访问限制,所以转换二义性和访问违规也就不会发生。结论是如果D继承自B,则选择yes_type check()。
    如果D不是继承自B,则对于static no_type check(B const volatile *, int)编译器的给出的转换为:
    C -> C const -> B const volatile*(UDC)
    对于static yes_type check(D const volatile *, T)编译器给出:
    C -> D const volatile* (UDC)
    这两个都不错(都需要一个UDC),然而由于static no_type check(B const volatile *, int)为非模板函数,所以被编译器选用。结论是如果D并非继承自B,则选择no_type check()。

    另外,在我的VC7.0环境下,如果将Host的operator B const volatile *() const的const拿掉,则结果将总是false。可惜这样的理解并不属于我,它们来自boost源代码中的注释。

    is_convertible(boost/type_traits/is_convertible.hpp)

    定义
    template< typename From >
    struct does_conversion_exist{
          template< typename To >
          struct result_{
              // 当不存在从From到To的任何转型时调用它
              static no_type _m_check(...);
              // 只要转型存在就调用它
              static yes_type _m_check(To);
              // 这只是个声明,所以并不占用空间,且没有开销。
              static From _m_from;
              enum {
                  value = sizeof( _m_check(_m_from) ) == sizeof(yes_type);
              };
         };
    };

    // 这是个为void准备的特化版本,因为不能声明void _m_from,只有void可以向void“转换”

    template<>
    struct does_conversion_exist<void>{
    template< typename To >
       struct result_{
          enum { value = ::boost::is_void<To>::value };
       };
    };

    // is_convertible完全使用does_conversion_exist作底层机制,所以略去。

    注解
    does_conversion_exist也使用了与is_class_impl一样的技术。所以注解从略。该技术最初由Andrei Alexandrescu发明。

    最后,Transformations Between Types(类型间转换),Synthesizing Types(类型合成),Function Traits(函数traits)的机制较为单纯,请自行参考boost提供的文档或头文件。

    traits是泛型世界中的精灵:小巧,精致。traits也是泛型编程中最精微的东西,它们往往仰赖于一些编译期决议的规则,C++标准,和神奇的模板偏特化。这也导致了它们在不同的平台上可能有不同表现,更常见的是,在某些平台上根本无法工作。然而,由于它们的依据是C++标准,而编译器会越来越符合标准,所以这些问题只是暂时的。traits也是构建泛型世界的基本组件之一,它们往往能使设计变得优雅,精致,甚至完美。

评论 (1)

请稍候...
很抱歉,您输入的评论太长。请缩短您的评论。
您没有输入任何内容,请重试。
很抱歉,我们当前无法添加您的评论。请稍后重试。
若要添加评论,需要您的家长授予您相应权限。请求权限
您的家长禁用了评论功能。
很抱歉,我们当前无法删除您的评论。请稍后重试。
您已超过了一天之内允许提供的评论数上限。请在 24 小时后重试。
因为我们的系统表明您可能在向其他用户提供垃圾评论,您的帐户已禁用了评论功能。如果您认为我们错误地禁用了您的帐户,请联系 Windows Live 支持部门
完成下面的安全检查,您提供评论的过程才能完成。
您在安全检查中键入的字符必须与图片或音频中的字符一致。

若要添加评论,请使用您的 Windows Live ID 登录(如果您使用过 Hotmail、Messenger 或 Xbox LIVE,您就拥有 Windows Live ID)。登录


还没有 Windows Live ID 吗?请注册

HuangYukai发表:
好奇你最近在干什么,更新blog这么勤快~
3 月 6 日

引用通告

此日志的引用通告 URL 是:
http://xietao.spaces.live.com/blog/cns!E51FB178705E0AD0!1744.trak
引用此项的网络日志