在之前的几讲里,我们已经多多少少接触到了一些 C++11 以来增加的新特性。下面的两讲,我会重点讲一下现代 C++(C++11/14/17)带来的易用性改进。
就像我们 [开篇词] 中说的,我们主要是介绍 C++ 里好用的特性,而非让你死记规则。因此,这里讲到的内容,有时是一种简化的说法。对于日常使用,本讲介绍的应该能满足大部分的需求。对于复杂用法和边角情况,你可能还是需要查阅参考资料里的明细规则。
自动类型推断
如果要挑选 C++11 带来的最重大改变的话,自动类型推断肯定排名前三。如果只看易用性或表达能力的改进的话,那它就是“舍我其谁”的第一了。
auto
自动类型推断,顾名思义,就是编译器能够根据表达式的类型,自动决定变量的类型(从 C++14 开始,还有函数的返回类型),不再需要程序员手工声明([1])。但需要说明的是,auto 并没有改变 C++ 是静态类型语言这一事实——使用 auto 的变量(或函数返回值)的类型仍然是编译时就确定了,只不过编译器能自动帮你填充而已。
自动类型推断使得像下面这样累赘的表达式成为历史:
// vector<int> v;
for (vector<int>::iterator
it = v.begin(),
end = v.end();
it != end; ++it) {
// 循环体
}
现在我们可以直接写(当然,是不使用基于范围的 for 循环的情况):
for (auto it = v.begin(), end = v.end();
it != end; ++it) {
// 循环体
}
不使用自动类型推断时,如果容器类型未知的话,我们还需要加上 typename(注意此处 const 引用还要求我们写 const_iterator 作为迭代器的类型):
template <typename T>
void foo(const T& container)
{
for (typename T::const_iterator
it = v.begin(),
…
}
如果 begin 返回的类型不是该类型的 const_iterator 嵌套类型的话,那实际上不用自动类型推断就没法表达了。这还真不是假设。比如,如果我们的遍历函数要求支持 C 数组的话,不用自动类型推断的话,就只能使用两个不同的重载:
template <typename T, std::size_t N>
void foo(const T (&a)[N])
{
typedef const T* ptr_t;
for (ptr_t it = a, end = a + N;
it != end; ++it) {
// 循环体
}
}
template <typename T>
void foo(const T& c)
{
for (typename T::const_iterator
it = c.begin(),
end = c.end();
it != end; ++it) {
// 循环体
}
}
如果使用自动类型推断的话,再加上 C++11 提供的全局 begin 和 end 函数,上面的代码可以统一成:
template <typename T>
void foo(const T& c)
{
using std::begin;
using std::end;
// 使用依赖参数查找(ADL);见 <span class="orange">[2]
for (auto it = begin(c),
ite = end(c);
it != ite; ++it) {
// 循环体
}
}
</span class="orange">
从这个例子可见,自动类型推断不仅降低了代码的啰嗦程度,也提高了代码的抽象性,使我们可以用更少的代码写出通用的功能。
auto 实际使用的规则类似于函数模板参数的推导规则([3]