4.3 decltype

类别:库作者

4.3.1 typeid与decltype

我们在4.2节中曾经提到过静态类型和动态类型的区别。不过与C完全不支持动态类型不同的是,C++在C++98标准中就部分支持动态类型了。如读者能够想象的,C++98对动态类型支持就是C++中的运行时类型识别(RTTI)。

RTTI的机制是为每个类型产生一个type_info类型的数据,程序员可以在程序中使用typeid随时查询一个变量的类型,typeid就会返回变量相应的type_info数据。而type_info的name成员函数可以返回类型的名字。而在C++11中,又增加了hash_code这个成员函数,返回该类型唯一的哈希值,以供程序员对变量的类型随时进行比较。我们可以看看代码清单4-16所示的例子。

代码清单4-16


include <iostream>

include <typeinfo>

using namespace std;

class White{};

class Black{};

int main(){

White a;

Black b;

cout<<typeid(a).name()<<endl;//5White

cout<<typeid(b).name()<<endl;//5Black

White c;

bool a_b_sametype=(typeid(a).hash_code()==typeid(b).hash_code());

bool a_c_sametype=(typeid(a).hash_code()==typeid(c).hash_code());

cout<<"Same type?"<<endl;

cout<<"A and B?"<<(int)a_b_sametype<<endl;//0

cout<<"A and C?"<<(int)a_c_sametype<<endl;//1

}

//编译选项:g++ -std=c++11 4-3-1.cpp


这里我们定义了两个不同的类型White和Black,以及其类型的变量a和b。此外我们使用typeid返回类型的type_info,并分别应用name打印类型的名字(5这样的前缀是g++这类编译器输出的名字,其他编译器可能会打印出其他的名字,这个标准并没有明确规定),应用hash_code进行类型的比较。在RTTI的支持下,程序员可以在一定程度上了解程序中类型的信息(这里可以注意一下,相比于4.1.2节中的is_same模板函数的成员类型value在编译时得到信息,hash_code是运行时得到的信息)。

除了typeid外,RTTI还包括了C++中的dynamic_cast等特性。不过不得不提的是,由于RTTI会带来一些运行时的开销,所以一些编译器会让用户选择性地关闭该特性(比如XL C/C++编译器的-qnortti,GCC的选项-fno-rttion,或者微软编译器选项/GR-)。而且很多时候,运行时才确定出类型对于程序员来说为时过晚,程序员更多需要的是在编译时期确定出类型(标准库中非常常见)。而通常程序员是要使用这样的类型而不是识别该类型,因此RTTI无法满足需求。

事实上,在C++的发展中,类型推导是随着模板和泛型编程的广泛使用而引入的。在非泛型的编程中,我们不用对类型进行推导,因为任何表达式中变量的类型都是明确的,而运算、函数调用等也都有明确的返回类型。然而在泛型的编程中,类型成了未知数。我们可以回顾一下4.2节中代码清单4-9所示的例子,其中的模板函数Sum的参数的t1和t2类型都是不确定的,因此t1+t2这个表达式将返回的类型也就不可由Sum的编写者确定。无疑,这样的状况会限制模板的使用范围和编写方式。而最好的解决办法就是让编译器辅助地进行类型推导。

在decltype产生之前,很多编译器的厂商都开发了自己的C++语言扩展用于类型推导。比如GCC的typeof操作符就是其中的一种。C++11则将这些类型推导手段进行了细致的考量,最终标准化为auto以及decltype。与auto类似地,decltype也能进行类型推导,不过两者的使用方式却有一定的区别。我们可以看代码清单4-17所示的这个简单的例子。

代码清单4-17


include <typeinfo>

include <iostream>

using namespace std;

int main(){

int i;

decltype(i)j=0;

cout<<typeid(j).name()<<endl;//打印出"i",g++表示int

float a;

double b;

decltype(a+b)c;

cout<<typeid(c).name()<<endl;//打印出"d",g++表示double

}

//编译选项:g++ -std=c++11 4-3-2.cpp


在代码清单4-17中,我们看到变量j的类型由decltype(i)进行声明,表示j的类型跟i相同(或者准确地说,跟i这个表达式返回的类型相同)。而c的类型则跟(a+b)这个表达式返回的类型相同。而由于a+b加法表达式返回的类型为double(a会被扩展为double类型与b相加),所以c的类型被decltype推导为double。

从这个例子中可以看到,decltype的类型推导并不是像auto一样是从变量声明的初始化表达式获得变量的类型,decltype总是以一个普通的表达式为参数,返回该表达式的类型。而与auto相同的是,作为一个类型指示符,decltype可以将获得的类型来定义另外一个变量。与auto相同,decltype类型推导也是在编译时进行的。