类型属性
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也是构建泛型世界的基本组件之一,它们往往能使设计变得优雅,精致,甚至完美。