欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

STL(三):traits

程序员文章站 2022-03-23 11:11:06
...

前言

在前面的介绍中,我们已经多次接触到traits,traits是一种泛型编程技法,用侯捷老师的话说,traits可以回答算法提出的问题,本文我们就来看看基本的traits技法和STL中的traits。

traits原理

泛型编程通过传入模板元的方法统一了不同类型的接口,在编译时,类型能够自动推导为传入的类型,这被称为泛化,比如下面的结构:

template <typename T>
struct A{
	bool value = true;
};

泛化时与传入的参数类型是无关的,无论T的类型是什么,value都被初始化为true。与泛化相对的概念叫特化,特化只当传入特定类型时才被调用。

template <>
struct A<int>{
	bool value = false;
};

我们来看看上面两个结构体的调用结果,测试代码如下:

#include <bits/stdc++.h>

template <typename T>
struct A{
	bool value = true;
};

template <>
struct A<int>{
	bool value = false;
};


int main(){
    A<bool> a1;
    A<int> a2;
    std::cout << "a1:" << a1.value << ",a2:" << a2.value << std::endl;
	return 0;
}

运行结果如下,可以看到与我们之前的预期一致。
STL(三):traits
有时并不需要完全的特化,比如程序需要仅对指针类型的特化,在上面的结构体中,可以这样写:

#include <bits/stdc++.h>

template <typename T>
struct A{
	int value = 1;
};

template <>
struct A<int>{
	int value = 2;
};

template <typename T>
struct A<T*>{
	int value = 3;
};


int main(){
    A<bool> a1;
    A<int> a2;
    A<char*> a3;
    std::cout << "a1:" << a1.value << ",a2:" << a2.value << ",a3:" << a3.value << std::endl;
	return 0;
}

运行结果如下:
STL(三):traits
传入的指针类型value为3,这种仅针对部分模板参数的特化称为偏特化。

Iterator_traits

Iterator_traits用于萃取Iterator的特性,我们先给出Iterator_traits的泛化版本:

template <class _Iterator>
struct iterator_traits {
  typedef typename _Iterator::iterator_category iterator_category; // 迭代器类别
  typedef typename _Iterator::value_type        value_type;  // 迭代器解除引用后所得到的值的类型
  typedef typename _Iterator::difference_type   difference_type; // 两个迭代器之间的距离
  typedef typename _Iterator::pointer           pointer;      // 指向被迭代类型的指针
  typedef typename _Iterator::reference         reference;   // 被迭代类型的引用类型
};

前面说过,traits就是用来回答算法提出的问题,Iterator通过将迭代器的五种类型内嵌到traits中,就可以回答算法的问题。但有时传入的不一定是迭代器,比如我们在STL(二):Iterator中见过传入C原生指针类型,为此traits还有几个偏特化版本:


// 针对原生指针(native pointer)而设计的 traits 偏特化版
template <class _Tp>
struct iterator_traits<_Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;  // C++ 内建的 ptrdiff_t 类型
  typedef _Tp*                        pointer;
  typedef _Tp&                        reference;
};

// 针对原生之 pointer-to-const 而设计的 traits 偏特化版
template <class _Tp>
struct iterator_traits<const _Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef const _Tp*                  pointer;
  typedef const _Tp&                  reference;
};

type_traits

type_traits用于萃取传入的参数是否具有”平凡“的特性,或者说是否是POD类型。POD类型名为Plain Old Data,意为简单旧数据,其实就是指最早的C语言类型,包括int、float、char、指针和struct等,这些数据结构的特点是可以直接通过二进制拷贝,并保持内容不变。
对于struct、union和class,既可能是POD类型,也可能不是,通常满足下面几个条件的数据结构是POD类型:

  • 不能写构造、析构、拷贝/移动构造函数、拷贝/移动运算符,只能使用编译器默认生成的;
  • 不能有虚函数和虚基类;
  • 普通数据成员必须有相同的访问权限,也就是说,普通数据成员必须全部是public/private/protect中的一种,不能分布在多种;
  • 第一个数据成员必须是自己的,不能来自其他类。

当然,可以直接使用STL的type_traits来判断是否是POD类型,这里也借助traits验证上面四个条件。

#include <bits/stdc++.h>

struct bad_case1{
    bad_case1(){};
};

struct bad_case2{
    virtual void foo(){};
};

struct  bad_case3{
public:
    int a;
private:
    int b;
};

struct base
{
    int a;
};

struct  bad_case4:public base{
    int b;
};

int main(){
    std::cout << "case1:" << std::is_pod<bad_case1>::value << std::endl;
    std::cout << "case2:" << std::is_pod<bad_case2>::value << std::endl;
    std::cout << "case3:" << std::is_pod<bad_case3>::value << std::endl;
    std::cout << "case4:" << std::is_pod<bad_case4>::value << std::endl;
    std::cout << "case5:" << std::is_pod<base>::value << std::endl;
	return 0;
}

运行结果如下:
STL(三):traits
具有POD类型的数据结构,可以直接拷贝二进制数据,因此可以使用C语言标准库中的memcopy函数,直接介绍过的allocator也正是利用这样的性质完成大块数据的填充和复制,可以看到STL在效率上精雕细琢,思路非常细腻。

相关标签: STL linux c++基础