感谢curise的精彩讲解,内容很短但是所有相关细节都讲的很清楚。learning cycle上有人担心不写明类型会使程序易读性变差,造成不必要的错误。会上时间有限,并没有做深入讨论,这里有必要说一下。
首先,我认为有这种担心的人是错误的理解了auto和decltype的定位和C++11引入他们的目的。C++引入自动的类型推导,并不是在向动态语言靠拢,通过弱化类型实现编程的灵活性。而是在保持类型安全的前提下提高代码的可读性,易用性和通用性。
要理解这点就必须对C++泛型编程(GP)和为什么要泛型有一定认识,推荐阅读:刘未鹏:泛型编程:源起、实现与意义。可以说模板和模板元编程的发展,带来了全新的C++,编译期多态的能力,使得C++代码可以做到通用行和执行效率的统一,这也是C++能在一些领域依然不可替代的原因。C++11的中很多改进就是要进一步加强和改进模板编程,因为模板编程有个很大的问题:难。不仅难写,而且难用。因为语言本身缺少variadic templates, concept等支持,模板库的作者被迫使用了很多tricky的方法,难度之大已经不是一般程序员能驾驭的了。而使用一个模板库,经常要写很长的表达式,不小心写错了,编译器就给爆出一堆近似与乱码的错误信息,调试起来更是头疼。C++引入auto, 就是要让模板的编写和使用更加容易。
auto并不是说这个变量的类型不定,或者在运行时再确定,而是说这个变量在编译时可以由编译器推导出来,使用auto和decltype只是占位符的作用,告诉编译器这个变量的类型需要编译器推导,借以消除C++变量定义和使用时的类型冗余,从而解放了程序员打更多无意义的字符,避免了手动推导某个变量的类型,甚至有时候需要很多额外的开销才能绕过的类型声明。
比如很多时候在用泛型算法的时候,并不在乎变量的类型,只关心这个变量能作什么。比如下面例子中:
1 | map< int, map<int,int> > _Map; |
我只需要知道itr2 是迭代器,能用来访问容器就够了,完全不需要打出长长的类型,万一打错,都不一定知道错在哪里,而且以后如果要修改_Map 的类型,所有的迭代器都要改, 完全不符合DRY原则。而使用auto 就避免这些问题。
auto并不是让程序员少打一些字这么简单,看下面例子:
1 | struct A |
C++98中,为了初始化变量C, 必须提供它的类型,所以我们用了三个typedef来做类型推导,在更复杂的模板代码中这个实现会更复杂的,这么做不仅非常麻烦,而且对用户代码是有侵入性的(使用这个函数的class 必须都定义自己的typedef)。其实在编译时,编译器完全可以自己推导出类型,只是为了符合C++类型声明和初始化的语法,才不得不让程序员替编译器做这些事。
1 | struct A |
所以顺理成章,C++11引入了auto, 可以交给编译器的工作,都交给编译器做,让程序员更focus在实现代码逻辑上。
关于何时应该使用auto, Andrei Alexandrescu, Scott Meyers 和Herb Sutter在C++ and Beyond 2012的Ask Us Anything panel上给出了意见:鼓励在所有地方使用auto,除非你需要做一个类型转换。当然他们做了很多解释,这段视频值得多看几遍。
相对与auto, decltype 更有针对性,具体用法参照里learning cycle PPT中的例子,这里不再累述。一句话就是:auto是为所有人准备的,而decltype是提供给模板开发者的。
下面是一些auto和decltype 的用法细节,摘抄自cruise的PPT:
auto variables have the type of their initializing expression:
1 | auto x1 = 10; |
const/volatile and reference/pointer adornments may be added:
1 | const auto *x2 = &x1; // x2: const int* |
To get a const_iterator, use the new cbegin container function:
1 | auto ci = m.cbegin(); |
For variables not explicitly declared to be a reference:
- Top-level consts/volatiles in the initializing type are ignored.
- Array and function names in initializing types decay to pointers.
1 | const std::list<int> li; |
Examples from earlier:
1 | auto x1 = 10; |
Yields the type of an expression without evaluating it.
1 | int x, *ptr; |
Fairly intuitive, but some quirks, e.g., parentheses can matter:
1 | struct S { double d; }; // double |
Primary use: template return types that depend on parameter types.
Common for forwarding templates:
1 | template<typename F, typename... Ts> |
Also in math-related libraries:
1 | template<typename T1, typename T2> |