《Effective Modern C++》翻译--条款4:了解怎样查看推导出的类型
条款4:了解怎样查看推导出的类型
那些想要了解编译器怎样推导出的类型的人通常分为两个阵营。
第一种阵营是实用主义者。他们的动力通常来自于编敲代码过程中(比如他们还在调试解决中),他们利用编译器进行寻找,并相信这个能帮他们找到问题的根源。另外一种是经验主义者。他们正在探索条款1-3所描写叙述的推导规则。
而且从大量的推导情景中确认他们预測的结果(“对于这段代码,我觉得推导出的类型将会是…”),可是有时候。他们仅仅是想简单的回答假设这样,会怎么样呢之类的问题?他们可能想知道假设我用一个universal reference(见条款26)替代一个左值的常量形參(比如在函数的參数列表中用T&&替代const T&)模板类型推导的结果会改变吗?
无论你属于哪一个阵营(二者都是合理的),你所要使用的工具取决于你想要在软件开发的哪一个阶段知道编译器推导出的结果。我们会阐述3种可行的方法:在编辑代码的时获得推导的类型。在编译时获得推导的类型,在执行时获得推导的类型。
IDE编辑器
当你在IDE中的编辑代码时,在你将鼠标悬停在程序实体(比如变量,參数。函数等等)上的时候。编译器显示他们的类型。
比如,在以下的代码中。
const int theAnswer = 42 ;
auto x = theAnswer;
auto y = &theAnswer;
IDE编辑器非常可能会显示出x的类型是int,y的类型是const int*。
对于这项工作。你的代码不能过于复杂,由于是IDE内部的C++编译器让IDE提供了这一项信息。
假设编译器不能充分理解并解析你的代码,产生类型推导的结果,它就无法给你显示类型推导的结果。
编译器的诊断
一个有效的得知编译器对某一类型推导出的结果方法是让它产生一个编译期的错误。由于错误的报告信息肯定会提到引起错误的类型。
假如,我们想要知道上一个代码中的x和y被推导出的类型。我们首先声明一个类模板,可是不定义它。代码会像以下这样:
template<typename T> //declaration only for TD
class TD; //TD == "Type Displayer"
试图实例化这个模板会产生一个错误信息,由于没有模板的定义和实例。为了要查看x和y的类型,仅仅须要用它们的类型实例化TD:
TD<decltype(x)> xType //elicit errors containing
TD<decltype(y)> yType //x's and y's types;
//see Item 3 for decltype info
我使用这样的形式的变量名:variableNameType。由于:它们趋向于产生足够实用的错误信息。对于上面的代码,当中一个编译器的错误诊断信息例如以下所看到的(我高亮了我们想要的类型推导结果)
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'; // display types for
std::cout << typeid(y).name() << '\n'; // x and y
这种方法依赖于typeid作用于一个对象上时,返回类型为std::type_info这一个事实,type_info有一个叫name的成员函数,提供了一个C风格的字符串(比如 const char*)来表示这个类型的名字。
调用std::type_info的name并不保证返回的东西一定是清楚明了的,可是会尽可能的提供帮助。
不同的编译器提供的程度各有不同,比如:GNU和Clang编译器将x的类型表示为”i”,将y的类型表示为”PKI”,一旦你了解i意味着int,pk意味着pointer to Konst const(两个编译器都提供一个C++ filt工具,来对这些重整后的名字进行解码)。理解编译器的输出将变得easy起来,Microsoft的编译器提供了更清楚的输出,x的类型是int,y的类型是int const*.
由于对x和y显示的结果是正确的,你可能会觉得问题已经攻克了。可是我们不能草率。想想以下这个更复杂的样例:
template<typename T> // template function to
void f(const T& param); // be called
std::vector<Widget> createVec(); // factory function
const auto vw = createVec(); // init vw w/factory return
if (!vw.empty()) {
f(&vw[0]); // call f
}
当你想知道编译器推导出的类型是什么的时候。这段代码更具有代表性,由于它牵涉到了一个用户自己定义类型widget,一个std容器std::vector。一个auto变量,比如。你可能想知道模板參数T的类型。和函数參数f的类型。
使用typeid看起来是非常直接的方法。仅仅是在f中对你想知道的类型加上一些代码:
template<typename T>
void f(const T& param)
{
using std::cout;
cout << "T = " << typeid(T).name() << '\n'; // show T
cout << "param = " << typeid(param).name() << '\n'; // show param's type
...
}
GNU和Clang的执行结果是以下这样:
T = PK6Widget
param = PK6Widget
我们已经知道PK意味着pointer to const,而6代表了类的名字中有多少个字母(Widget),所以这两个编译器告诉了我们T和param的类型都是const Widget*
微软的编译器提供了以下的结果
T = class Widget const *
param = class Widget const *
这三个编译器都提供了一样的信息。这也许暗示了结果应该是准确的。可是让我们看的更仔细一点,在模板f中,param的类型被声明为constT&,既然如此的话,param和T的类型一样难道不让人感到奇怪吗,假设T的类型是int,param的类型应该是const int&,看,一点都不一样。
令人悲哀的是std::type_info::name的结果并非可依赖的。在这个样例中,三个编译器对于param的结果都是不对的。此外。它们必须是错误的。由于标准(specification)规定被std::type_info::name处理的类型是被依照按值传递给模板对待的,像条款1解释的那样。这意味着假设类型本身是一个引用的话,引用部分是被忽略掉的,假设引用去掉之后还含有const,常量性也将被忽略掉,,这就是为什么const Widget* const &的类型被显示为const Widget*,首先类型的引用部分被忽略了,接着结果的常量性也被忽略了。
相同令人伤心的是,IDE提供的类型信息相同也是不可靠的,或者说不是那么的实用,对于这个样例,我所知道的编译器将T的类型显示为(这不是我编造出来的):
const
std::_Simple_types<std::_Wrap_alloc<std::_Vec_base_types<Widget,
std::allocator<Widget> >::_Alloc>::value_type>::value_type *
将param的类型显示为:
const std::_Simple_types<...>::value_type *const &
这个显示没有T的那么吓人了。中间的…仅仅是意味着IDE告诉你。我将T的类型显示用…替代了。
我的理解是大多数显示在这里的东西是由于typedef造成的,一旦你通过typedef来获得潜在的类型信息,你会得到你所寻找的。但须要做一些工作来消除IDE最初显示出的一些类型,幸运的话, 你的IDE编辑器会对这样的代码处理的更好。
在我的经验中,使用编译器的错误诊断信息来知道变量被推导出的类型是相对可靠的方法,利用修订之后的函数模板f来实例化仅仅是声明的模板TD。修订之后的f看起来像以下这样
template<typename T>
void f(const T& param)
{
TD<T> TType; // elicit errors containing
TD<decltype(param)> paramType; // T's and param's types
…
}
GNU。Clang和Microsoft的编译器都提供了带有T和param正确类型的错误信息,当时显示的格式各有不同,比如在GUN中(格式经过了一点轻微的改动)
error: 'TD<const Widget *> TType' has incomplete type
error: 'TD<const Widget * const &> paramType' has incomplete
type
除了typeid
假设你想要在执行时获得更正确的推导类型是什么,我们已经知道typeid并非一个可靠的方法,一个可行的方法是自己实现一套机制来完毕从一个类型到它的表示的映射,概念上这并不困难。你仅仅须要利用type trait和模板元编程的方法来将一个完整类型拆分开(使用std::is_const,std::is_ponter,std::is_lvalue_reference之类的type trait),你还须要自己完毕类型的每一部分的字符串表示(虽然你依然须要typeid和std::type_info::name来产生用户自己定义格式的字符串表达)。
假设你常常须要使用这种方法,而且觉得花费在调试,文档,维护上的努力是值得的。那么这是一个合理的方法(If you’d use such a facility often enough to justify the effort needed to write, debug,document, and maintain it, that’s a reasonable approach),可是假设你更喜欢那些移植性不是非常强的可是能轻易实现而且提供的结果比typeid更好的代码的。 你须要注意到非常多编译器都提供了语言的扩展来产生一个函数签名的字符串表达,包含从模板中实例化的函数,模板和模板參数的类型。
比如。GNU和Clang都支持PRETTY_FUNCTION,Microsoft支持了FUNCSIG,他们代表了一个变量(在 GNU和Clang中)或是一个宏(在Microsoft中),假设我们将模板f这么实现的话
template<typename T>
void f(const T& param)
{
#if defined(__GNUC__) //For GNU and
std::cout << __PRETTY_FUNCTION__ << '\n'; // Clang
#elif defined(_MSC_VER)
std::cout << __FUNCSIG__ << '\n'; //For Microsoft
#endif
…
}
像之前那样调用f,
std::vector<Widget> createVec(); // factory function
const auto vw = createVec(); // init vw w/factory return
if (!vw.empty()) {
f(&vw[0]); //call f
...
}
在GNU中我们得到了以下的结果
void f(const T&) [with T = const Widget*]
告诉我们T的类型被推导为const Widget*(和我们用typeid得到的结果一样,可是前面没有PK的编码和类名前面的6),同一时候它也告诉我们f參数类型是const T&,假设我们依照这个格式扩展T,我们得到f的类型是const Widget * const&,和typeid的答案不同,可是和使用没有定义的模板,产生的错误诊断信息中的类型信息一致。所以它是正确的。
Microsoft的 FUNCSIG提供了以下的输出:
void __cdecl f<const classWidget*>(const class Widget *const &)
尖括号中的类型是T被推导的类型,为const Widget*。
和我们用typeid得到的结果一样。
括号内的类型是函数參数的类型。是const Widget* const&。和我们用typeid得到的结果不一样。但相同和我们使用TD在编译期得到的类型信息一致。
Clang的PRETTY_FUNCTION,虽然使用了和GNU一样的名字,可是格式却和GNU或是Microsoft的不一样:
void f(const Widget *const &)
它直接显示出了參数的类型,可是须要我们自己去推导出T的类型被推导为了const Widget*(或者我们也能够通过typeid来获得T的类型)
IDE编辑器。编译器的错误诊断信息,typeid和PRETTY_FUNCTION,FUNCSIG之类的语言扩展仅仅仅仅是帮助你弄明确编译器推导出的结果是什么。可是最后,没有什么能替代条款1-3中所描写叙述的类型推导相关的推导规则。
请记住:
•能够通过使用IDE编译器、编译错误信息、typeid、PRETTY_FUNCTION和FUNCSIG这样的语言扩展等。查看类型推导。
•一些工具提供的类型推导结果可能既没实用也不准确,所以理解C++类型推导的原则十分必要。
==============================================================
译者凝视:
IDE 即Integrated Development Environment。是“集成开发环境”的英文缩写,能够辅助开发程序的应用软件。
《Effective Modern C++》翻译--条款4:了解怎样查看推导出的类型的更多相关文章
- Effective Modern C++翻译(7)-条款6:当auto推导出意外的类型时,使用显式的类型初始化语义
条款6:当auto推导出意外的类型时,使用显式的类型初始化语义 条款5解释了使用auto来声明变量比使用精确的类型声明多了了很多的技术优势,但有的时候,当你想要zag的时候,auto可能会推导出了zi ...
- Effective Modern C++翻译(4)-条款3:了解decltype
条款3 了解decltype decltype是一个有趣的东西,给它一个变量名或是一个表达式,decltype会告诉你这个变量名或是这个表达式的类型,通常,告诉你的结果和你预测的是一样的,但是偶尔的结 ...
- Effective Modern C++翻译(3)-条款2:明白auto类型推导
条款2 明白auto类型推导 如果你已经读完了条款1中有关模板类型推导的内容,那么你几乎已经知道了所有关于auto类型推导的事情,因为除了一个古怪的例外,auto的类型推导规则和模板的类型推导规则是一 ...
- Effective Modern C++翻译(2)-条款1:明白模板类型推导
第一章 类型推导 C++98有一套单一的类型推导的规则:用来推导函数模板,C++11轻微的修改了这些规则并且增加了两个,一个用于auto,一个用于decltype,接着C++14扩展了auto和dec ...
- Effective Modern C++翻译(5)-条款4:了解如何观察推导出的类型
条款4:了解如何观察推导出的类型 那些想要知道编译器推导出的类型的人通常分为两种,第一种是实用主义者,他们的动力通常来自于软件产生的问题(例如他们还在调试解决中),他们利用编译器进行寻找,并相信这个能 ...
- Effective Modern C++翻译(1):序言
/*********************************************************** 关于书: 书是我从网上找到的effective Modern C++的样章,内 ...
- Effective Modern C++翻译(6)-条款5:auto比显示的类型声明要更好
在概念上说,auto关键字和它看起来一样简单,但是事实上,它要更微妙一些的.使用auto会让你在声明变量时省略掉类型,同时也会防止了手动类型声明带来的正确性和性能上的困扰:虽然按照语言预先定义 ...
- Effective Modern C++ 条款1:理解模板型别推导
成百上千的程序员都在向函数模板传递实参,并拿到了完全满意的结果,而这些程序员中却有很多对这些函数使用的型别是如何被推导出的过程连最模糊的描述都讲不出来. 但是当模板型别推导规则应用于auto语境时,它 ...
- Effective Modern C++:01类型推导
C++的官方钦定版本,都是以ISO标准被接受的年份命名,分别是C++98,C++03,C++11,C++14,C++17,C++20等.C++11及其后续版本统称为Modern C++. C++11之 ...
随机推荐
- AT1145 ホリドッグ
洛谷的题解区里竟然没有O(1)做法详解-- 题面就是要判断\(1+2+\dots+n\)是不是素数 很容易让人想到上面的式子事实上等于\(n(n+1)/2\) 根据质数的定义,质数只能被1和自身整除 ...
- CF735E Ostap and Tree
比较毒瘤的树形DP,子状态难想.这是主要是搬运一篇题解. 用\(f[i][j]\)表示\(i\)的子树中离\(i\)最近黑点的距离为\(j\),且距离超过\(j\)的点都被满足的方案数.转移时新建一个 ...
- dubbo 部分 配置的关系-dubbo github 官方案例
1.dubbo 有一个 dubbo.properties 作为默认配置 默认配置可以在不添加新的配置的前提下使用dubbo dubbo.properties 的内容(来自 https://github ...
- Maven多模块项目搭建
最近一直在思考如何能够更好的重用代码.减少重复劳动,之前有一篇文章通过导入JAR包的形式,可以重用部分形如util类的方法,但是这样的话,管理起来jar包,特别是协同工作,多项目情况下,管理JAR会出 ...
- volatile 和 mutable 关键字
经常接触,但是过一段时间可能又忘了.做个记录. volatile是表示变量易变,不要放缓存,每次实际取,尤其是多线程. mutable表示一个const 类或者数据结构里面,某个字段是可以改变的.
- nyoj--914--Yougth的最大化(二分查找)
Yougth的最大化 时间限制:1000 ms | 内存限制:65535 KB 难度:4 描述 Yougth现在有n个物品的重量和价值分别是Wi和Vi,你能帮他从中选出k个物品使得单位重量的价值最 ...
- systemd服务管理--systemctl常用命令
转自 :https://m635674608.iteye.com/blog/2359480 1. #systemctl enable crond.service //让某个服务开机启动(.servic ...
- Array数组的排序与二分查字法
import java.util.Arrays; public class sort { public static void main(String[] args) { // TODO 自动生成的方 ...
- jquery简介 each遍历 prop attr
一.JQ简介 jQuery是一个快速.简洁的JavaScript框架,它封装了JavaScript常用的功能代码,提供一种简便的JavaScript设计模式,优化HTML文档操作.事件处理.动画设计和 ...
- 用canvas画一个的小画板(PC端移动端都能用)
前言 本篇的内容主要包括: canvas标签简介 画板的功能简介 画板的JS部分(包括:1.获取画布 2.使画板全屏幕显示且自适应 3.如何绘制直线 4.绘画时的三种状态(鼠标点击.移动.离开)5.画 ...