Effective Modern C++ 条款4 懂得如何查看已推断类型

##懂得如何查看已推断类型
一些工具可以让我们看到推断类型的结果,但是这些工具的选择取决于软件开发的不同阶段。在这节,我们会讨论获取类型推断信息的3个可能的阶段:写代码的时候,编译期间,运行期间。


###IDE 编辑器
在一些IDE编辑器中,当你把鼠标放在一些代码(例如变量,参数,函数)上时,IDE可以显示出它们的类型。例如下面的代码:

const int theAnswer = 42;
auto x = theAnswer;
auto y = &theAnswer;

一些IDE编辑器会直接显x 的推断类型是inty 的推断类型是**const int ***。

要想得到这种效果,你的代码必须放在一个差不多是可编译的地方,因为IDE编辑器中呈现的这种效果,是因为有一个编译器运行在IDE中。如果编译器没能理解你写的代码就无法编译进行类型推断,那么IDE是不能显示出代码的类型的。

对于像int这种简单的类型,IDE很容易推断出来。但是很快我们就会看到,当涉及一些较为复杂的类型时,IDE的显示效果就没什么用了。


###编译器诊断
一个让编译器展示它推断的类型的有效方法是,用它需要推断的类型引发编译错误。编译器报错信息中会展示出引发错误的代码类型。

例如,我们想要上一段代码中xy 的推断类型。我们声明一个类模板但是不定义它,如下:

template <typename T>
class TD;

当我们尝试去实例化这个模板时,会引发错误,因为类模板还没有定义。我们用xy 的类型去实例化模板,就可以知道xy的推断类型了:

TD<decltype(x)> xType;
TD<decltype(y)> yType;

我把变量声明为 变量名+Type(variableNameType),因为这样产生的错误信息让我很容易找到。对于上面的代码,我的一个编译器诊断出来错误,并显示以下错误:
error: aggregate 'TD xType' has incomplete type and cannot be defined
error: aggregaet 'TD yType' has incomplete type and cannot be defined
另一个编译器提供的报错信息是这样的:
error: 'xType' uses undefined class 'TD'
error: 'yType' uses undefined class 'TD'

当使用这项技术时,虽然说不同编译器提供的信息不一样,但是我测试的所有编译器都会提供有用的信息。


###运行时输出
通过打印到终端的方法来显示已推断类型只能在运行程序时使用。用这个方法的问题是需要为你的类型创建一个合适的字符串表示,用来输出。你可能认为使用typeid关键字和**std::type_info::name()**很容易解决这个问题。在我们继续探讨如何查看xy 的类型前,你可能会认为我们会写出这样的代码:

std::cout << typeid(x).name << std::endl;
std::cout << typeid(y).name << std::endl;

这个方法对xy 使用typeid关键字,然后产生了一个std::type_info对象,而std::type_info又有一个成员函数name,这个成员函数会返回一个c语言风格的字符串(const char*),用来表示xy 的类型。

调用**std::type_info::name()**无法保证明智地返回我们想要的东西,但它的实现是为了返回一些对我们有用的东西。例如,对于上面的代码,GUN和Clang的编译器对于x的结果输出"i",对于y的结果输出"PKi"。这些编译运行的结果只要你学习一下就能理解,比如"i"的意思是"int""PKi"的意思是"pointer to konst(const)"。而微软的编译器的输出信息会易懂一下x 的结果是"int"y 的结果是"int const *"

因为上面的代码运行的结果是正确的,你可能已经认为类型报告的问题已经解决了,但是不用急,让我们来看一个更复杂的例子:

template <typename T>
void f(const T& param);

std::vector<Widget> createVec();

const auto vw = createVec();

if (!vw.empty()) {
    f(	&vw[0]);
    ...
}

这段代码涉及到用户自己定义的数据类型(Widget),一个STL容器(std::vector),还有一个auto变量(vw),这些类型推断情形在你的代码中更为典型。如果能知道模板中T 的类型和参数param 的类型对于我们来说将会很方便。

我们明确知道typeid会返回模糊的结果,所以我们可以加一下代码来让我们更易观察:

template <typename T>
void f(const T& param)
{
    using std::cout;
    cout << "T =     " << typeid(T).name() << '\n';
    cout << "param = " << typeid(param).name() << '\n';
    ...
}

用GUN和Clang的编译器编译运行会输出结果如下:
T = PK6Widget
param = PK6Widget
我们已经知道"PK"的意思是"pointer to const",所以现在搞不懂的是数字6,而这个6只是说明类名的字节数(Widget)。所以编译器告诉我们Tparam 的类型都是**const Widget ***。

而微软的编译器输出结果是:
T = class Widget const *
param = class Widget const *

3种编译器产生了相同的结果表明typeid的信息是准确的。但我们仔细看,在模板函数f中,param的声明类型是const T&,那么在这个例子中,Tparam的类型不觉得奇怪吗?就像如果T 的类型是int,那么param 的类型应该是const int&,而不是相同的类型。

很遗憾地说,std::type_info::name()返回的结果是不可靠的。就像上面的例子,3个不同的编译器对param 报告的类型都是不正确的。而且,他们本质上就是不对的,因为std::type_info::name()的行为是对待类型就像在模板中以传值方式声明参数(这段话我知道意思,可是不知道怎样翻译容易理解:because the specification for std::type_info::name mandates that the type be treated as if had been passed to a template function as a by-value parameter.)。正如条款1中的解释,这是属于第3种情况,忽略顶层constvolatile,这就是为什么param 的类型本该是const Widget * const & 被报告成了 const Widget *** 。这是因为一开始移去了引用语义**,然后忽略了顶层const

个人理解为什么param 的类型是const Widget * const &
首先vw的类型是const vector,vw[0]的类型是const Widget,然后取地址得到指针类型const Widget *,接着传入模板,而模板的参数声明是const T&,直接匹配,因为&vw[0]本是非常量指针类型,所以param先变成了常量指针,最后加上引用,得到结果,并且得到T 的类型为const Widget *

同样很遗憾,IDE编辑器遇到上面那个例子也无法正确的显示出类型,因为IDE会显示出很复杂的结果(略去这部分)。


如果你更愿意依赖库而非运气的话(因为IDE和typeid都有点运气成分),你愿意接受IDE和**std::type_info::name()**可能的失败,因为Boost TypeIndex库设计得非常成功。这个库文件不是标准C++的内容,但是IDE和功能类似TD的模板也不属于标准C++。而且Boost库有很多优势。

下面给出使用Boost.TypeIndex库而写出的正确代码:

#include 
template <typename T>
void f(const T& param)
{
    using std::cout;
    using boost::typeindex::type_id_with_cvr;
    cout << "T =     "
         << type_id_with_cvr<T>().pretty_name()
         << '\n';
                        `
    cout << "param = "
         << type_id_with_cvr<decltype(param)>().pretty_name();
         << '\n';
    ...`
}

模板函数boost::typeindex::type_id_with_cvr需要个类型(在这里是我们想要知道的类型)用来实例化,而且它不会删去该类型已有的constvolatile引用语义(所以后缀带有"with_cvr")。这个模板函数的返回结果是个boost::typeindex::type_index对象,该对象有一个成员函数pretty_name会生成一个对人友好的std::string来表示这个类型。

用这个模板函数f的实现,再次运行我们的测试程序,在GUN和Clang的编译器下运行结果为:
T = Widget const*
param = Widget const* const&
在微软的编译器下运行的结果一致:
T = class Widget const *
param = class Widget const * const &


##总结
这种查看已推断类型的方法对我们来说很美好,但是我们需要记住IDE编辑器、编译器报错信息和类似Boost.TypeIndex的库都仅仅是一个帮你找出编译器推断的类型结果的工具。虽说这些工具都对我们有帮助,但是,这所有的工具终究不能取代条款1~3里面的类型推断本质的理解。

需要记住的2点:

  • 通常可以使用IDE编辑器,编译器报错信息和Boost TypeIndex库来查看已推断的类型。
  • 一些工具的结果可能没有帮助或者不准确,所以理解透彻C++类型推断规则才是知道正确已推断类型的本质。

你可能感兴趣的:(Effective,Modern,C++,Effective,Modern,C++,c++)