STL(三):traits
前言
在前面的介绍中,我们已经多次接触到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;
}
运行结果如下,可以看到与我们之前的预期一致。
有时并不需要完全的特化,比如程序需要仅对指针类型的特化,在上面的结构体中,可以这样写:
#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;
}
运行结果如下:
传入的指针类型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;
}
运行结果如下:
具有POD类型的数据结构,可以直接拷贝二进制数据,因此可以使用C语言标准库中的memcopy函数,直接介绍过的allocator也正是利用这样的性质完成大块数据的填充和复制,可以看到STL在效率上精雕细琢,思路非常细腻。