编程技术是改变世界的力量。
本站
当前位置:网站首页 > 后端语言 > 正文

C++|深入理解STL类型萃取机制(type traits)

gowuye 2024-04-25 04:43 9 浏览 0 评论

我们知道Traits是C++语言的一种高级特性。STL首先利用Traits技术对迭代器的特性做出规范,制定出iterator_traits(参考:STL算法如何萃取(traits)迭代器型别(value_type)?)。后来SGI STL把它应用在迭代器以外的地方, 就有了type_traits。

函数、类或者一些封装的通用算法中的某些部分会因为数据类型不同而导致处理或逻辑不同(而我们又不希望因为数据类型的差异而修改算法本身的封装时)。traits会是一种很好的解决方案。

由于模板的泛型特性,并不是全部类型都能适应统一泛型定义,如果我们为每种类型都添加上特化处理,可以想象得到,代码会膨胀成怎样。另外,对于不同的类定义,可能需要定义不定的成员函数,如对于包含指针(指向堆内存)的自定义类,需要重新定义拷贝构造、拷贝赋值、析构函数,对于不包含指针的类,这些函数则无需定义和调用,否则会引起性能的损耗。

Traits 是一种 “可用于编译器在编译时根据型别作判断” 的泛型技术,像在执行期根据数值执行判断一样。

不要像用switch一样枚举各个类型,用traits用模板实现。

type traits是算法用来获取对象的一些特征信息,比如说是不是class,是不是function,有没有const、signed、volatile这些修饰符,有没有无用的构造函数之类的技术,根据对象的这些信息就可以分别实现相关的操作。

1 类型特性判断

先来看C++模板编程和编译器的参数推导功能(编译器只有面对类类型参数才会进行参数推导):

#include <iostream>

template <typename T>    
struct is_void
{ 
    static const bool value = false; // 所有类型value = false
};

template <> 
struct is_void<void>
{ 
    static const bool value = true;  // void类型进行了特化,使value = true
};

int main()
{
    std::cout<<is_void<int>::value;    // 输出0,所有非void类型都会输出0
    std::cout<<is_void<void>::value; // 输出1
    return 0;
}

加上一个中间层,能够进行特定类型识别。

C++11中,<type_traits>实现了很多的Type Traits的模板类:

如is_const:

#include <iostream>
#include <type_traits>
 
int main(){
    int a;
    const int b = 3;
    std::cout << std::is_const<decltype(a)>::value << std::endl;    // 1
    std::cout << std::is_const<decltype(b)>::value << std::endl;    // 0
}

is_const实现代码(利用模板匹配特性):

/// is_const
  template<typename>       // 泛型版本
    struct is_const : public false_type { };       
 
  template<typename _Tp> // 特化版本
    struct is_const<_Tp const>: public true_type { };     

两个版本的父类false_type和true_type是两个helper class,其定义如下:

// integral_constant,提供一个编译期常量,类似enum和static const变量方式
template<typename _Tp, _Tp __v> // 推导出bool类型及其值
struct integral_constant
{
    static constexpr _Tp value = __v;
    typedef _Tp value_type;
    typedef integral_constant<_Tp, __v>   type;
    constexpr operator value_type() const noexcept { return value; } // 用于类型转换
    constexpr value_type operator()() const noexcept { return value; } // 用于仿函数
};

// The type used as a compile-time boolean with true value.
typedef integral_constant<bool, true> true_type; // true_type::value=1

// The type used as a compile-time boolean with false value.
typedef integral_constant<bool, false> false_type;

value_type 表示值的类型。
value表示值。
type 表示自己, 因此可以用::type::value来获取值。
true_type和false_type两个特化类用来表示bool值类型的traits,很多traits类都需要继承它们。

如果不想细看代码,也可以简单地说,true_type和false_type就是包含一个静态类成员value的类模板,其静态成员一个为true,一个为false,仅此而已。

我们不能将参数设为bool值, 因为需要在编译期就决定该使用哪个函数, 所以需要利用函数模板的参数推导机制(编译器只有面对类类型参数才会进行参数推导), 将__true_type和__false_type表现为一个空类, 就不会带来额外的负担, 又能表示真假, 还能在编译时类型推导就确定执行相应的函数。

对于is_const,通过特化,如果我们使用const类型作为模板is_const类型参数,则可以获得其常量静态成员value的值为true(1)。这是因为模板在实例化的时候选择了“版本2”。反过来,如果模板实例化到“版本1”,则value常量静态成员为false(0)。

Integral_constant使用实例:

#include <typeinfo>
#include <iostream>
// #include <type_traits>
using namespace  std;

template<typename _Tp, _Tp __v> // 推导出bool类型及其值
struct Integral_constant
{
    static constexpr _Tp value = __v;
    typedef _Tp value_type;
    typedef Integral_constant<_Tp, __v>   type;
    constexpr operator value_type() const noexcept { return value; } // 用于类型转换
    constexpr value_type operator()() const noexcept { return value; } // 用于仿函数
};

// The type used as a compile-time boolean with true value.
typedef Integral_constant<bool, true> True_type; // True_type::value=1

// The type used as a compile-time boolean with false value.
typedef Integral_constant<bool, false> False_type;

template <unsigned n>
struct factorial : Integral_constant<int ,  n * factorial<n - 1>::value>
{};
template <>//特化
struct factorial<0> : Integral_constant<int, 1>
{};

int main()
{
    typedef   Integral_constant<int, 111>   IC; 
    cout << "value " << IC::value << endl; // 111
    cout << "value_type  " << typeid(IC::value_type).name() << endl; // i
    cout << "type " << typeid(IC::type).name() << endl; // St17Integral_constantIiLi111EE
    IC  ic;
    int  c1 = ic; //调用的operator value_type()
    cout << c1 << endl; // 111
    int c2 = ic();//调用的 operator()()
    cout << c1 << endl; // 111
    cout << Integral_constant<char, 'a'>::value <<endl; // a
    cout << True_type::value << endl; // 1
    
    cout << Integral_constant <bool,false>::value << endl; // 0
    cout << factorial<5>::value<<endl; // 120
}

类型萃取雏形:

#include <iostream>
using namespace std;

struct True_type {};
struct False_type {}; 

struct A{};
struct B{};

template <class type>   // type_traits
struct type_traits {
    typedef False_type has_xxx;   // 默认为False_type
};

template <>  // 特化A
struct type_traits<A> {
    typedef True_type has_xxx;
}; 

template <>  // 特化B
struct type_traits<B> {
    typedef False_type has_xxx;
}; 

template <class T>
void test(T t) {              // test()调用_test()
    typedef typename type_traits<T>::has_xxx  has_x;
    _test(has_x());
};

void _test(True_type) { // _test()版本1
    cout << "1" << endl;
}

void _test(False_type) { // _test()版本2
    cout << "0" << endl;
}

int main() {
    struct A a;
    struct B b;
    test(a);    // 输出1
    test(b);    // 输出0
    test(1);    // 输出0
    test(3.5);  // 输出0
    return 0;
}
// type traits用来萃取元素特性,如果元素具有某个性质则do_something,否则do_otherthing。
// 这个例子里对类类型A、B进行了特化,只有A类型里has_xxx(某个性质)为true_type,
// 向函数test()传递参数T时,type_traits进行特性萃取,将T中的has_xxx 赋予别名has_x,
// 而在类型A中对true_type赋予别名has_xxx,所以这里的has_x 就是true_type类型,调用函数_test(),
// 函数_test()有两个版本,根据参数进行匹配,参数为true_type类型,输出1。
// 调用test(b)、test(1)、test(3.5)输出0是一样的道理。

再来看is_pod:

#include <iostream>
#include <type_traits>
template<typename T>
bool is_pod(T) { 
    return std::is_pod<T>::value;  // plain old data, C primitive type
}

int main(){
    int a;
    std::cout << std::is_pod<int>::value<<std::endl; // 1
    std::cout << std::is_pod<decltype(a)>::value<<std::endl; // 1
    std::cout << is_pod(a); // 1
}

我们可以看看g++4.8.1中POD的定义:

// is_pod
// Could use is_standard_layout && is_trivial instead of the builtin.
template<typename _Tp>
struct is_pod
: public integral_constant<bool, __is_pod(_Tp)>
{ };

这里的__is_pod就是编译器内部的intrinsic。所以说,并非所有的Type Traits都能够使用元编程的手段来实现。C++语言设计者在实践中进行了一些考量,让部分的Type Traits实现为了intrinsic,简单地说,就是要编译器辅助来计算出其值。总的来说,Type Traits就是通过元编程的手段,以及编译器的辅助来实现的。

2 类型萃取与函数派送机制

类型萃取使用模板技术来萃取类型(包含自定义类型和内置类型)的某些特性,用以判断该类型是否含有某些特性,从而在泛型算法中来对该类型进行特殊的处理用来提高效率或者其他。

例如在STL中的destory算法根据函数的参数类型的特性是否有trivial destructor来选择对应的策略来进行destory,如果为内置类型,则不调用该类型的destructor,这样更有效率。否则对迭代器范围内的对象调用destructor来进行destory。

这就是使用类型萃取能提供一种根据类型的某些属性在编译时期进行函数派送的机制。

当容器进行范围destoy的时候,其函数接受first和last的迭代器,若[first,last)范围内所有的元素都调用析构函数,但这个类型T的析构又是无关痛痒的,则会损耗效率。 type_trais<T>可以判别该类型T的的析构函数是否无关痛痒,若是(true_type),则什么都不做,否则调用其析构函数:

 // 接受两个迭代器, 以__type_trais<> 判断是否有traival 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());
}
// non-travial destructor 
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) 
{
  for ( ; first < last; ++first)
    destroy(&*first);
}
// travial destructor
template <class ForwardIterator> 
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}

在最上层的destory函数通过value_type全局函数来对迭代器萃取迭代器所指向的对象的类型,然后调用__destory,该函数通过类型萃取来判断该类型是否含有trivial destructor来在编译时期进行函数派送。函数派送机制通过模板的编译机制和模板函数的重载来避免在函数代码使用if和else语句或者switch来判断是否有trivial destructor从而进行函数派送,避免了效率损失和代码的混乱。

3 SGI-STL中type traits可以萃取的类型属性

在SGI-STL中,可以萃取的类型属性如下:

has_trivial_default_constructor
has_trivial_copy_constructor
has_trivial_assignment_operator
has_trivial_destructor
is_POD_type

Type Traits实现的基础是函数模板+偏特化+编译器辅助,对特定类型做出特定的定义:

Type Traits首先定义了_true_type和_false_type这两个structure。然后给出了泛化和针对int和double的两个特化版本。可以看出,因为int和double都是Plain Old Data(POD)类型,所以它们的default_constructor, copy_constructor, assignment_operator, destructor 这些东西都不重要,其实根本就不需要,所以它们的has_trivial_xxxx都被定义为_true_type。而泛化版本的这些缺省都是 _false_type。这样,当算法询问一个 POD类型的对象有没有copy_contructor的时候, 就知道答案为否。

我们看看对于一个比较简单的class(不含指针),它的这些has_trival_xxxx会返回什么呢?我们可以测试一下这个Foo类:

class Foo
{
    private:
    int d1, d2;
};

测试结果

__has_trivial_assign 1

__has_trivial_copy 1

__has_trivial_constructor 1

__has_trivial_destructor 1

这个符合预期,因为class foo里面没有指针,所以上面4个函数都不重要,C++编译器给它们提供的缺省函数就够了。

我们再测试一下list,返回结果为

__has_trivial_assign 0

__has_trivial_copy 0

__has_trivial_constructor 0

__has_trivial_destructor 0

这个也符合预期, 因为list里面有指针嘛。

type_traits的源代码实现如下:

/**
 * 用来标识真/假对象,利用type_traits响应结果来进行参数推导,
 * 而编译器只有面对class object形式的参数才会做参数推导,
 * 这两个空白class不会带来额外负担
 */
struct __true_type{};
struct __false_type{};

/**
 * type_traits结构体设计
 */
template <class type>
struct __type_traits
{
    // 不要移除这个成员
    // 它通知能自动特化__type_traits的编译器, 现在这个__type_traits template是特化的
    // 这是为了确保万一编译器使用了__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;
};
// 特化类型:
//         char, signed char, unsigned char,
//         short, unsigned short
//         int, unsigned int
//         long, unsigned long
//         float, double, long double
/**
 * 以下针对C++内置的基本数据类型提供特化版本, 
 * 使其具有trivial default constructor,
 * copy constructor, assignment operator, destructor并标记其为POD类型
 */
__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;
};
//针对char的特化版本
__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;
};
//针对unsigned char的特化版本
__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;
};
//针对short的特化版本
__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;
};
//针对unsigned short的特化版本
__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;
};
//针对int的特化版本
__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;
};
//针对unsigned int的特化版本
__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;
};
//针对long的特化版本
__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;
};
//针对unsigned long的特化版本
__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;
};
//针对float的特化版本
__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;
};
//针对double的特化版本
__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;
};
//针对long double的特化版本
__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;
};

// 针对指针提供特化
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;
};

// 针对char *, signed char *, unsigned char *提供特化
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>:

ref:

roufoo:C++ Type Traits的学习

Coding_Reading:STL源码剖析——type traits编程技法

-End-

相关推荐

R语言数据挖掘实践——支持向量机的常用函数
R语言数据挖掘实践——支持向量机的常用函数

e1071包是R语言中用于支持向量机建模与分析的软件包,其主要用于支持向量机的模型构建,提供核心函数svm()来建立支持向量机的基础模型,并且可辅助使用pred...

2024-05-18 12:15 gowuye

R数据分析:如何做聚类分析,实操解析
R数据分析:如何做聚类分析,实操解析

Clusteringisabroadsetoftechniquesforfindingsubgroupsofobservationswi...

2024-05-18 12:14 gowuye

用R语言做数据分析——马赛克图
用R语言做数据分析——马赛克图

到目前为止,我们已经学习了许多可视化定量或连续型变量间关系的方法。但如果变量是类别型的呢?若只观察单个类别型变量,可以使用柱状图或者饼图;若存在两个类别型变量,...

2024-05-18 12:14 gowuye

用R语言做数据分析——方差分析基本概论
用R语言做数据分析——方差分析基本概论

在实际工作中,影响一件事的因素是很多的,我们总是希望通过各种试验来观察各种因素对试验结果的影响。例如,不同的生产厂家、不同的原材料、不同的操作规程,以及不同的技...

2024-05-18 12:14 gowuye

R语言数据分析实战:数据清洗与可视化
R语言数据分析实战:数据清洗与可视化

《R语言数据分析实战:数据清洗与可视化》是一本深入浅出的实践指南,专为对数据分析感兴趣的读者精心编撰。本书旨在帮助读者掌握R语言这一强大的统计分析工具,通过实例...

2024-05-18 12:13 gowuye

用R语言做数据分析——双因素方差分析
用R语言做数据分析——双因素方差分析

在双因素方差分析中,受试者被分配到两因子的交叉类别组中。以基础安装中的Tooth-Growth数据集为例,随机分配60只豚鼠,分别采用两种喂食方法(橙汁或维生素...

2024-05-18 12:13 gowuye

用R语言做数据分析——独立两样本和K样本检验
用R语言做数据分析——独立两样本和K样本检验

coin包简介对于独立性问题,coin包提供了一个进行置换检验的一般性框架,通过这个包,我们可以回答如下问题:响应值与组的分配独立吗?两个数值变量独立吗?两个类...

2024-05-18 12:13 gowuye

用R语言做数据分析——用回归做方差分析
用R语言做数据分析——用回归做方差分析

之前提到方差分析和回归都是广义线性模型的特例,之前文章的所有设计都可以用lm()函数来分析。为了更好地理解输出结果,需要弄明白在拟合模型时,R语言是如何处理类别...

2024-05-18 12:13 gowuye

数据分析R语言——数据结构
数据分析R语言——数据结构

数据分析R语言——数据结构数组数组(array)与矩阵类似,但是维度可以大于2.数组通过array()函数创建。形式如;myarray<-array(v...

2024-05-18 12:13 gowuye

R语言数据挖掘实践——关联分析的常用函数
R语言数据挖掘实践——关联分析的常用函数

arules和arulesViz是R语言中两个专用于关联分析的软件包。其中arules用于关联规则的数字化生成,提供Apriori和Eclat这两种快速挖掘频繁...

2024-05-18 12:12 gowuye

R语言数据挖掘实践——判别分析的常用函数
R语言数据挖掘实践——判别分析的常用函数

判别算法在R语言中实现主要涉及4个软件包中的相关函数,它们依次为MASS、klaR、class和kknn。其中MASS包含有大量实用而先进的统计计数函数及适用数...

2024-05-18 12:12 gowuye

用R语言读取Excel、PDF和JSON文件,终于有人讲明白了
用R语言读取Excel、PDF和JSON文件,终于有人讲明白了

导读:本文将讨论Excel、PDF等文件的读取,以及相应函数的参数设置。作者:刘健邬书豪如需转载请联系华章科技下图总结了主要程序包,希望读者在日常练习和工作中...

2024-05-18 12:12 gowuye

R语言数据挖掘实践——聚类分析的常用函数
R语言数据挖掘实践——聚类分析的常用函数

使用R语言可以轻松实现聚类分析,stats、cluster、fpc和mclust是常用的四个聚类分析软件包。stats主要包含一些基本的统计函数,如用于统计计算...

2024-05-18 12:12 gowuye

用R语言做数据分析——时间序列分类
用R语言做数据分析——时间序列分类

时间序列分类是根据已标注的时间序列建立一个分类模型,然后使用分类模型预测未标记时间序列的类别。从时间序列中抽取出新特征肯呢个有助于提高分类模型的性能。特征提取技...

2024-05-18 12:11 gowuye

一文看懂用R语言读取Excel、PDF和JSON文件(附代码)
一文看懂用R语言读取Excel、PDF和JSON文件(附代码)

导读:本文将讨论Excel、PDF等文件的读取,以及相应函数的参数设置。作者:刘健邬书豪如需转载请联系华章科技下图总结了主要程序包,希望读者在日常练习和工作中...

2024-05-18 12:11 gowuye

取消回复欢迎 发表评论: