C++标准库笔记:《The C++ Standard Library Second Edition》读书笔记1
5.4 Type Traits and Type Utilities
类型检测是现代程序设计中非常重要的特性,这些工具可以为程序员提供关于类型的各种信息,我们将这些信息称为“元数据”。许多现代的静态语言和动态语言都通过不同的特性来支持运行时查看元数据,但是作为一个完完全全的静态语言,C++中的支持却非常有限(不记录任何类型信息)。
在C++11中,为了部分地弥补这个缺陷,加入了type_traits工具。这些工具可以帮助用户在编译时确定一些类型信息(注意,是编译时,而非运行时)。
编译时类型检测原理
比如书中介绍的第一个函数就是std::is_pointer<T>,该模板类用于检测某个类型是否为指针,如果为真则等价于true_type,如果为假则等价于false_type,其::value静态成员函数值为true或者false。
很多人会疑惑,为什么不直接将其设计为模板函数,并直接返回true或者false呢?比如:
1 template <typename T> 2 3 void foo(const T& val) { 4 5 std::cout << (std::is_pointer<T>() ? *val : val) 6 7 << std::endl; 8 9 }
但是大家注意,C++是静态语言,也就是说无论T是不是指针类型,*val这个表达式都会在编译时被执行,而如果val不是指针,就会导致编译出错。
所以is_pointer经过了精心设计,不是简单地返回一个值(动态语言中这是可行的),而是通过与某一个静态类型等价来完成任务,这些静态类型就是true_type和false_type了。
我们可以使用以下代码完成上面代码的功能:
1 template <typename T> 2 void foo_impl(const T& val, std::true_type) { 3 4 std::cout << *val << std::endl; 5 6 } 7 8 template <typename T> 9 10 void foo_impl(const T& val, std::false_type) { 11 12 std::cout << val << std::endl; 13 14 } 15 16 template <typename T> 17 18 void foo(const T& val) { 19 20 foo_impl(val, std::is_pointer<T>()); 21 22 }
大家发现了,其实C++11编译时类型检测的核心在于我们可以使用编译器的模板类型推导来根据实际情况选择执行哪一段代码!
因此,is_pointer被定义成具体的类型,而不是一个返回true或者false的函数——解决类型信息问题的方法还是要用到类型信息,只不过是间接通过编译器类型推导实现的。
编译时类型检测目的
有些人看了上面的代码会觉得,既然是利用模板类型推导,那么使用C++98的特性就足以完成任务:
1 template <typename T> 2 3 void foo(const T& val); 4 5 template <typename T> 6 7 void foo<T*>(const T& val);
大家会注意到上面我们利用了模板类型的偏特化来解决这个问题,如果T是一个指针,那么就会执行下面一个函数。
那么我们又为什么需要这一套类型检测工具呢?
其实类型检测的真正威力在于给一类类型提供了通用代码复用的能力。
比如来看看下面这个:
1 void foo(short); 2 3 void foo(unsigned short); 4 5 void foo(int); 6 7 ... 8 9 void foo(float); 10 11 void foo(double); 12 13 void foo(long double);
其实我们的目的是浮点类型执行一个动作,而整数类型执行另一个动作,明显我们要为每一个具体类型重载一个函数吗,会产生大量冗余代码。
但是如果利用type_traits,这件事情就变得非常简单了:
1 template <typename T> 2 3 void foo_impl(T val, true_type); 4 5 template <typename T> 6 7 void foo_impl(T val, false_type); 8 9 template <typename T> 10 11 void foo(T val){ 12 13 foo_impl(val, std::is_integral<T>()); 14 15 }
我们发现我们可以利用type_traits工具为一类类型生成一个对应类型,然后只需要对那种类型进行函数重载即可,可以省略大量冗余代码。