item 4: 知道怎么去看推导的类型
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢!
博客已经迁移到这里啦
对于推导类型结果的查看,根据不同的软件开发阶段,你想知道的信息的不同,可以选择不同的工具。我们将探讨三种可能性:在你编辑代码时获得类型推导信息,在编译期获得信息,在运行期获得信息。
IDE 编辑器
在IDE中编辑代码常常能显示程序实体(比如,变量,参数,函数等)的类型,只需要你做一些像把光标放在实体上面之类的事。举个例子,给出这样的代码:
const int theAnswer = 42;
auto x = theAnswer;
auto y = &theAnswer;
一个IDE编辑器可能会显示x的推导类型为int,y的推导类型为int*。
为了像这样工作,你的代码肯定或多或少处于编译状态,因为只有C++编译器(或者至少是一个编译器前端)在IDE底层运行才能给IDE提供这样的类型推导信息。如果编译器不能成功分析和执行类型推导来明确你的代码,它就不能显示他推导的类型。
对于一些简单的类型,比如int,IDEs给出的信息通常是对的。然而,就像我们马上要看到的那样,当涉及更加复杂的类型时,IDEs显示的信息可能就没什么帮助了。
编译器诊断
这里有一个有效的方法,让编译器显示它推导的类型,那就是把这个类型用在会导致编译错误的地方。错误信息报告错误的时候常常会涉及到造成这个错误的类型。
假设,为了举个例子,我们想看看之前例子中的x和y被推导成什么类型。我们可以先声明一个class template但是不去定义它。就好像这样漂亮的代码:
template<typename T> //只是声明TD
class TD; //TD == type displayer
//类型显示器
尝试实例化这个template将引起错误,因为我们没有相应的定义来实例化。为了看x和y的类型,只要使用它们的类型尝试实例化TD:
TD<decltype(x)> xType; //引起错误,错误会包含
TD<decltype(x)> yType; //x和y的类型
我使用variableNameType的形式来命名变量名字,因为它们在错误信息输出的时候出现,并帮我找到我要找的信息。对于上面的代码,我的其中一个编译器的一部分关于类型判断的输出就在下面(我已经高亮显示了类型信息)(译注:就是int和const int *)
error: aggregate 'TD<int> xType' has incomplete type and
cannot be defined
error: aggregate 'TD<const int *> yType' has incomplete type
and cannot be defined
一个不同的编译器提供了相同的信息,但是以不同的形式显示:
error: 'xType' uses undefined class 'TD<int>'
error: 'yType' uses undefined class 'TD<const int *>'
先把格式的不同放在一边,当使用这种方法时,我尝试的所有编译器都产出带有有用类型信息的错误消息。
运行期输出
直到运行期前,printf都不能显示类型信息(并不是说我推荐你使用printf),但是它提供对输出格式的所有控制。这里有个难点,就是如何把你想知道的类型用适合显示的文本来输出。你可能觉得,“没问题,typeid和std::type_info::name会拯救我们。”在我们接着探讨如何查看x和y的类型推导前,你可能觉得我们能像下面这样写:
std::cout<<typeid(x).name()<<'\n'; //显示x的y的类型
std::cout<<typeid(y).name()<<'\n';
这个方法依赖于一个事实,那就是对x和y使用typeid,能产生一个std::type_info的对象,并且这个std::type_info对象有一个成员函数,name,它能产生一个C风格的字符串(也就是一个 const char*)来代替类型的名字。
调用std::type_info::name不能保证返回任何明显易懂的类型,但是实现尽量保证有用。有用的情况是不同的,GNU和Clang编译器报告x的类型是“i”,并且y的类型是“PKi”。如果你学过,这些返回信息将是有意义的,来自编译器的这些输出,“i”意味着“int”,“PK”意味着“point to konst(谐音const)”(两个编译器都支持一个工具,c++filt,这工具能解析这些“残缺的”类型)。微软的编译器产生更明确的输出:x是“int”,y是“int const*”.
因为这些x和y的类型结果都是对的,你可能觉得类型显示的问题已经被解决了,但是别这么轻率。考虑下更加复杂的例子:
template<typename T> //需要调用的template函数
void f(const T& param);
std::vector<Widget> createVec();//工厂函数
cosnt auto vw = createVec(); //初始化vw
if(!vw.empty()){
f(&vw[0]); //调用f
...
}
这段代码涉及了一个user-defined的类型(Widget),一个STL的容器(std::vector)和一个auto变量(vw)。这是一个典型的auto类型,你可能想直观地看一下你的编译器会推导出什么类型。例如,如果能看下template参数类型T和函数参数param的类型将非常棒。
使用粗糙的typeid是很直接的,只要加一些代码到f中来显示你想看的类型:
template<typename T>
void f(cosnt T& param)
{
using std::cout;
cout<<"T = "<< typeid(T).name()<< '\n'; //显示T
cout<<"param = "<< typeid(param).name()<< '\n';//显示param
}
GNU和Clang编译器产生的可执行文件产生这样的输出:
T = PK6Widget
param = PK6Widget
我们已经知道对这些编译器,PK意味着“pointer to cosnt”,所以剩下的谜题就是数字6。它简单地表示class名字(Widget)的长度。所以这些编译器告诉我们T和param都是const Widget*的类型。
微软的编译器同样:
T = class Widget cosnt *
param = class Widget cosnt *
这三个独立的编译器产生了同样的信息暗示结果是准确的。但是看得再仔细一些,在template f中,param的声明类型是const T&。这就是问题所在了,T和param的类型是相同的,这看起来不是很奇怪吗?举个例子,如果T是int,那么param应该是const int&,它们并不是一样的类型。
很不幸,std::type_info::name的结果是不可靠的。在这种情况下,例子中的三个编译器对类型的解读都是错误的。另外,本质上,它们都应该要是错误的,因为std::type_info::name的说明书上说,传入的类型会以传值(by-value)的方式传入一个template函数。就像item 1解释的那样,这意味着如果类型是一个引用,他的引用属性会被忽略,并且在去掉引用属性后,是const(或volatile)的,它的const(或volatile)属性也会被忽略。这就是为什么param的类型(cosnt Widget * const&)被报告称cosnt Widget*。指针的引用属性和const属性都被消除了。
同样不幸的是IDE显示的类型信息也是不可靠的,或者至少说是没有用处。在这个例子中,一个我知道的IDE编辑器报告T的类型是(我不是胡编乱造):
cosnt
std::Simple_types<std::Wrap_alloc<std::_Vec_base_types<Widget,
std::allocator<Widget> >::_Alloc>::value_type>::value_type *
同样的IDE编辑器显示param的类型:
const std::_Simple_types<...>::value_type *const &
这没有T的类型那么吓人,但是中间的“...”让你感到困惑,除非你了解到,这个IDE编辑器说出“我省略所有的T表示的东西”。如果运气好的话,你的开发环境会做一个更好的工作。
如果比起运气,你更倾向于依赖库的话,你将会很高兴知道std::type_info::name和IDE都失败的事情,Boost TypeIndex library(常写作Boost.TypeIndex)却成功了。这个库不是标准C++库的一部分,但是IDE和template TD也不是。另外,事实上Boost库(可以从boost.com获取)是跨平台的,开源的,
只要你在license下设计,即使是最偏执的团队(要求很高的可移植性)也能很容易设计出漂亮的程序。这意味着代码中用了Boost库的可移植性同依赖于标准库的可移植性是几乎相同的。
这里给出我们的函数f怎么用Boost.TypeIndex产出正确的类型信息:
#include <boost/type_index.hpp>
template<typename T>
void f(const T& param)
{
using std::cout;
using boost::typeindex::type_id_with_cvr;
// show T
cout << "T = "
<< type_id_with_cvr<T>().pretty_name()
<< '\n';
// show param's type
cout << "param = "
<< type_id_with_cvr<decltype(param)>().pretty_name()
<< '\n';
…
}
这段代码工作的方式是,function template boost::typeindex::type_id_with_cvr使用一个类型参数(我们想显示信息的类型)并且不移除const,volatile,或引用属性(因此template名字后面带着with_cvr)。结果是一个boost::typeindex::type_index对象,这个对象有一个pretty_name的成员函数,产生一个std::string对象,里面是我们易读的并且符合具体类型的字符串。
用这个f的实现,再看看那个用typeid会对param产生错误类型信息的函数调用:
std::vector<Widget> createVec();
cosnt auto vw = createVec();
if(!vw.empty()){
f(&vw[0]);
...
}
在GNU和Clang编译器下,Boost.TypeIndex 产生这个(准确的)输出:
T = Widget const*
param = Widget const* const&
微软的编译器产生的结果本质上是相同的:
T = class Widget const*
param = class Widget const* const&
这样的“几乎一致”是好的,但是记住IDE编辑器,编译器错误消息和像Boost.TypeIndex这样的库是几乎所有的工具你能用来帮助你找出你的编译器推导出来的类型。但是,最后说一句,没有任何东西能替代items 1-3中对类型推导的理解。
你要记住的事
- 类型的推导常常能在IDE编辑器,编译器的错误消息,Boost TypeIndex库中看到
- 一些工具的返回结果可能没有帮助或不准确,所以理解C++的类型推导规则是必不可少的
item 4: 知道怎么去看推导的类型的更多相关文章
- 擦亮自己的眼睛去看SQLServer之简单Insert(转)
摘要:本来是打算先写SQLServer历史的,不过感觉写那部分内容比较难还需要多查些资料.于是调整了下顺序写下简单的Insert语句. 不过感觉写那部分内容比较难还需要多查些资料.于是调整了下顺序写下 ...
- h5 plus/h5+规范使用,模块索引,教你如何去看h5+的手册
最近看了下h5+规范的官网,开始觉得晦涩难懂,确实很乱,不过这也是基于我不理解的情况,终于艰难读完了,现在来分享下心得吧,基本看完文章,按我的方法,应该可以直接上手项目. 我准备的工具 hbuilde ...
- 我追一个处女座的女孩快两个月了,我之前聊得很好,她说过有空call我去看电影,过了一个月她就不理我了,我喜欢她, 我是程序员,百度发不了那么多字。
她刚刚进公司的时候,公司组织去打球,我叫她一起去她也去了,我和她聊了很多,聊得很自然,很开心,如我是哪个学习毕业的 我出来工作多久了等,她也聊了 她自己好多,她现在在读大学,只有周日上一天课那种. 我 ...
- Codevs 1570 去看电影
1570 去看电影 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题解 查看运行结果 题目描述 Description 农夫约翰带着他的一些奶牛去看 ...
- codevs——1570 去看电影
1570 去看电影 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题解 题目描述 Description 农夫约翰带着他的一些奶牛去看电影.而他的 ...
- Hadoop Hive概念学习系列之HDFS、Hive、MySQL、Sqoop之间的数据导入导出(强烈建议去看)
Hive总结(七)Hive四种数据导入方式 (强烈建议去看) Hive几种数据导出方式 https://www.iteblog.com/archives/955 (强烈建议去看) 把MySQL里的数据 ...
- thinkphp中view页面中的volist标签转化为原生php分析(多去看源代码,你会发现不仅简单,方便你理解,还节约时间)
thinkphp中view页面中的volist标签转化为原生php分析(多去看源代码,你会发现不仅简单,方便你理解,还节约时间) 一.总结 1.标签和原生php之间的关系:标签只是为了方便你使用,标签 ...
- TypeScript 类型推导及类型兼容性
类型推导就是在没有明确指出类型的地方,TypeScript编译器会自己去推测出当前变量的类型. 例如下面的例子: let a = 1; 我们并没有明确指明a的类型,所以编译器通过结果反向推断变量a的类 ...
- 初窥C++11:自己主动类型推导与类型获取
auto 话说C语言还处于K&R时代,也有auto a = 1;的写法.中文译过来叫自己主动变量.跟c++11的不同.C语言的auto a = 1;相当与 auto int a = 1;语句. ...
随机推荐
- JavaScript大杂烩16 - 推荐实践
JavaScript部分 1. 总是使用===来进行相等判断 原因:由于 == 和 != 操作符存在类型转换问题,而为了保持代码中数据类型的完整性,推荐使用全等 === 和不全等 !=== 操作符. ...
- 洗礼灵魂,修炼python(59)--爬虫篇—httplib模块
httplib 1.简介 同样的,httplib默认存在于python2,python3不存在: httplib是python中http协议的客户端实现,可以用来与 HTTP 服务器进行交互,支持HT ...
- Django2.0.1开发框架搭建
1.使用vs2017创建空白django项目 2.右键python环境的env---安装python包 升级django到2.0.1和setuptools到38.4.0版本,具体环境如下: 3.配置 ...
- 转:Vue2.0+组件库总结
UI组件 element - 饿了么出品的Vue2的web UI工具套件 Vux - 基于Vue和WeUI的组件库 mint-ui - Vue 2的移动UI元素 iview - 基于 Vuejs 的开 ...
- "error lnk1158 无法运行rc.exe”解决方案
最近使用VS2012编译时,出现" error lnk1158 无法运行rc.exe”的问题,无法编译生成.exe文件,连最基本的HelloWorld控制台程序都无法运行,重置了VS的默认设 ...
- arcgis如何求两个栅格数据集的差集
栅格数据集没有擦除功能,现在有栅格A和栅格B,怎么求两个栅格的差集C 具体步骤如下: 1.首先利用栅格计算器,把栅格B中的value全部赋值为0 输入语句:"栅格B" * 0 2 ...
- vue实例详解
Vue实例的构造函数 每个 Vue.js 应用都是通过构造函数 Vue 创建一个 Vue 的根实例 启动的 虽然没有完全遵循 MVVM 模式, Vue 的设计无疑受到了它的启发.因此在文档中经常会使用 ...
- 字典树模板题(统计难题 HDU - 1251)
https://vjudge.net/problem/HDU-1251 标准的字典树模板题: 也注意一下输入方法: #include<iostream> #include<cstdi ...
- ethereum/EIPs-1077 Executable Signed Messages
https://github.com/alexvandesande/EIPs/blob/ee2347027e94b93708939f2e448447d030ca2d76/EIPS/eip-1077.m ...
- 2018-2019-2 20165302程上杰 Exp6 信息搜集与漏洞扫描
1,实践目标 掌握信息搜集的最基础技能与常用工具的使用方法. 2.,实验内容 (1)各种搜索技巧的应用 (2)DNS IP注册信息的查询 (3)基本的扫描技术:主机发现.端口扫描.OS及服务版本探测. ...