STL源码剖析——Iterators与Traits编程#5 __type_traits

唐宋源码清 2019-10-21

上节给出了iterator_traits以及用到traits机制的部分函数的完整代码,可以看到traits机制能够提取迭代器的特性从而调用不同的函数,实现效率的最大化。显然这么好的机制不应该仅局限于在STL里面使用,在前某一节中我们也有说到,traits机制能够萃取类的特性,而这个类分为两个类别,一是迭代器类,二普通类,迭代器类我们已经有所了解了,那么本节就来学习负责萃取普通类型特性的__type_traits吧。

于普通类型而言,我们所关注的特性是指:这个类型是否具备non-trivial defalt ctor?是否具备non-trivial copy ctor?是否具备non-trivial assignment ctor?是否具备non-trivial dtor?在关注__typr_traits之前,我们很有必要来讨论一下上述的几个词组是什么意思。其中ctor为构造函数,那么copy ctor就是拷贝构造函数,assignment ctro为赋值构造函数,defalt ctor为默认构造函数。而trivial的意思为琐碎的、无意义的。那么判断这4个构造函数是否有意义的条件是:如果至少满足一下了3条中的1条,那么就是说明其类是有意义(non- trivial)的:

  1. 显式(explict)定义了这四种函数
  2. 类里有非静态非POD的数据成员
  3. 有基类

何为POD?简单解释就是指C风格的struct结构体定义的数据结构或者C++的内建类型,POD类型必然有trivial ctor/dtor/copy/assignment四种函数。

那么区分构造函数是否有意义又有什么意义呢?

如果这个类都是trivial ctor/dtor/copy/assignment函数,我们对这个类进行构造、析构、拷贝和赋值时可以采用最有效率的方法,是不调用无所事事正真的那些ctor/dtor等,而是直接采用内存操作如malloc()、memcpy()等提高性能,获取最高效率。这对于大规模而操作频繁的容器,有着显著的效率提升。

而这个__type_traits机制就能针对不同的类特性,在编译时期完成函数的调用决定。这对于template很有帮助,例如,当我们准备对一个类型特性未知的数组进行copy操作时,如果我们能事先知道其元素的类型特性是否有一个trivial copy ctor,便能够帮我们我们决定是否可使用快速的memcpy()或memmove()。

与使用iterator_traits相似,我们可以这样运用__type_traits<T>:

__type_traits<T>::has_trivial_default_constructor
__type_traits<T>::has_trivial_copy_constructor
__type_traits<T>::has_trivial_assignment_operator
__type_traits<T>::has_trivial_destructor
__type_traits<T>::is_POD_type

我们希望通过调用这些式子来告诉我们T类型是否是有意义的(以便我们决定采取什么策略),但其结果不应该只是个bool值,应该是个有着真/假性质的“对象”,因为我们希望利用其相应结果来进行参数推导(等会给出代码例子),而编译器只有面对class object形式的参数,才会做参数推导。为此,上述式子应该传回这样的东西:

1 struct __true_type {};
2 struct __false_type {};

这两个空白的结构体没有任何东西,不会带来额外的负担,却又能标示真假,满足我们所需。

为此,我们应该为上述的五个式子定义一些typedef,准确告知T是否有意义:

template <class type>
struct __type_traits {
    typedef __true_type     this_dummy_member_must_be_first;
    /*不要移除这个成员,它通知「有能力自动将 __type_traits特化」的编译器说,我们现在所看到的这个 __type_traits template 是特殊的。这是为了确保万一编译器也使用一个名为 __type_traits而其实与此处定义并无任何关联的template时,所有事情仍将顺利运作*/
    
    /* 以下条件应该被遵守,因为编译器有可能自动为各类型产生专属的 __type_traits特化版本:
    - 你可以重新排列以下的成员次序
    - 你可以移除以下任何成员
    - 绝对不可以将以下成员重新命名而却没有改变编译器中的对应名称
    - 新加入的成员会被视为一般成员,除非你在编译器中加上适当支援
    */
    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;
    // 所谓 POD 意指 Plain Old Data structure. 
};

this_dummy_member_must_be_first:因为某些编译器会提供__type_traits机制,自动会为所有类型提供适当的特化版本,所以为了将编译器内部的__type_traits和STL 自带的__type_traits区分开来,提供该特殊的定义式。

为什么SGI把所有的内嵌类型都定义为__false_type呢?这是最保守的做法,它默认认为所有的自定义类型都是具有有意义的构造函数,至于内部基本类型,SGI STL会为其提供特化版本。总的来说,上述的__type_traits可以接受任何类型的参数,但五个typedef会经由以下的管道获得实值:

  • 自定义类型,内含对所有类型都必定有效的保守值。上述各个has_trivial_xxx型别都被定义为__false_type,就是对所有类型都必定有效的保守值。
  • 经过声明的特化版本,例如<type_traits.h>内对所有C++基本类型提供了对应的特化声明。稍后展示。
  • 某些编译器会自动为所有类型提供适当的特化版本。

以下便是<type_traits.h>对所有C++基本类型所定义的__type_traits特化版本。这些定义对于内建有__type_traits支持能力的编译器并无伤害,对于无该支持能力的编译器而言,实属必要。

/* 以下针对 C++ 基本型別 char, signed char, unsigned char, short, unsigned short, 
int, unsigned int, long, unsigned long, float, double, long double 提供特化版本。
注意,每一个成员的值都是 __true_type,表示这些型別都可采用最快速方式(例如 memcpy)
來进行拷贝动作或赋值动作。*/

//注意,SGI STL<stl_config.h>将以下出现的 _STL_TEMPLATE_NULL
//定义为template<>,是所谓的class template explicit specialization

__STL_TEMPLATE_NULL struct __type_traits<char> {
    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_TEMPLATE_NULL struct __type_traits<signed char> {
    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_TEMPLATE_NULL struct __type_traits<unsigned char> {
    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_TEMPLATE_NULL struct __type_traits<short> {
    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_TEMPLATE_NULL struct __type_traits<unsigned short> {
    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_TEMPLATE_NULL struct __type_traits<int> {
    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_TEMPLATE_NULL struct __type_traits<unsigned int> {
    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_TEMPLATE_NULL struct __type_traits<long> {
    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_TEMPLATE_NULL struct __type_traits<unsigned long> {
    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_TEMPLATE_NULL struct __type_traits<float> {
    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_TEMPLATE_NULL struct __type_traits<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;
};

__STL_TEMPLATE_NULL 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;
};

#ifdef __STL_CLASS_PARTIAL_SPECIALIZATION

template <class T>
struct __type_traits<T*> {
    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;
};

#else /* __STL_CLASS_PARTIAL_SPECIALIZATION */

struct __type_traits<char*> {
    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;
};

struct __type_traits<signed char*> {
    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;
};

struct __type_traits<unsigned char*> {
    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;
};

对于__type_traits的应用,我们可以举一个之前学习Allocator时就遇到的函数destroy():

// 以下是 destroy() 第二版本,接受兩個迭代器。此函式是設法找出元素的數值型別,
// 進而利用 __type_traits<> 求取最適當措施。
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
  __destroy(first, last, value_type(first));
}

// 判斷元素的數值型別(value type)是否有 trivial destructor
template <class ForwardIterator, class T>
inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {
  typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
  __destroy_aux(first, last, trivial_destructor());
}

// 如果元素的數值型別(value type)有 non-trivial destructor…
template <class ForwardIterator>
inline void
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
  for ( ; first < last; ++first)
    destroy(&*first);
}

// 如果元素的數值型別(value type)有 trivial destructor…
template <class ForwardIterator> 
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}

可以看到,在__destroy函数里,利用了__type_traits<T>机制判断T类型是否具有无意义的析构函数,然后再根据判断结果进行参数推导,调用相应的函数,如果是无意义的析构函数就不做很任何事情,提升性能;如果是有意义的析构函数,就为该区间里的所有对象逐一调用析构函数。

正如上所言,于一切自定义类型,__type_traits<T>机制默认认为其具有有意义的构造函数(除非是使用内部提供__type_traits机制的编译器就能自动识别该类型是否有意义,但大部分编译器缺乏这种特异功能),这样的结果过于保守,那么如何让__type_traits<T>机制能够为我们的自定义类型提取到真正的特性呢?答案就是自行为自己设计的类型提供__type_traits特化版本,明白地告诉编译器事实。举例,假设我自行定义了一个shape class,是一个没有默认构造函数的类型(即存在trivial defalt ctor),那么它的__type_traits特化版本应该是:

template<> struct __type_traits <Shade> {
    typedef __true_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-trivial defalt ctor,non-trivial copy ctor,non-trivial assignment ctor,non-trivial dtor?一个简单的判断准则就是:如果类内包含指针成员,并且对它进行内存动态配置,那么这个类就需要实现出自己的non-trivial-xxx。

相关推荐