原文出处:http://www.cprogramming.com/c++11/c++11-auto-decltype-return-value-after-function.html
C++11 引入了几种新的类型推断,可以让你为那些编译器本来就应该知道的事实写更少的代码。当然,我们也需要在必要的时间帮助编译器或者其他开发人员,但是使用 C++ 11,你可以在那些无聊的工作上花费较少的时间,而关注于业务逻辑。
我们从最明显的新特性:auto
关键字开始讲起。
auto
的乐趣
下面为那些没有阅读过 C++0x 的第一篇文章的读者简要介绍下auto
关键字。在 C++11 中,如果编译器可以在声明的位置推断出变量的类型,那么,你就可以不写出变量类型,只使用auto
关键字:
int x = 4; // 现在可以这么写 auto x = 4;
当然,这完全看不出auto
的强大之处。当我们结合模板和遍历器的时候,就可以看出其闪光点了:
vector<int> vec; auto itr = vec.iterator(); // 不需要写 vector<int>::iterator itr
也有适合auto
出现的另外的地方。例如,假设你需要类似下面类型的代码:
template <typename BuiltType, typename Builder> void makeAndProcessObject (const Builder& builder) { BuiltType val = builder.makeObject(); // 使用 val 干点什么... }
在这个代码中,我们有两个必须的模板参数:一个是构建器对象自己的类型,第二个是被构建的对象的类型。更糟糕的是,被构建的对象类型不能声明为目标参数。每次调用时,我们必须这么写:
MyObjBuilder builder; makeAndProcessObject<MyObj>( builder );
但是,auto
立马消除了这种难看的代码,因为你再也不需要在构建对象时指明类型。让 C++ 帮你做吧:
template <typename Builder> void makeAndProcessObject (const Builder& builder) { auto val = builder.makeObject(); // 使用 val 干点什么... }
现在,你只需要一个模板参数,这个参数可以在调用函数时轻松推断出来:
MyObjBuilder builder; makeAndProcessObject( builder );
这对调用者更加友好,从可读性方面说,模板代码也没什么损失——如果说有的话,那也是更容易理解的!
decltype
和新返回值语法的乐趣
现在你可能会说——好吧,不错,但如果我要返回这个构建器对象构建出来的对象呢?我还是得提供一个模板参数,因为我需要提供返回值。好吧,事实证明,标准委员会都是聪明人,他们提供了一套漂亮的解决方案解决这个问题。这个解决方案分为两部分:decltype
和新的返回值语法。
新的返回值语法
我们先来看看新的、可选的返回值语法,因为它是auto
的另外一种应用。在 C 和 C++ 之前版本中,函数的返回值出现在函数之前:
int multiply (int x, int y);
在 C++11 中,如果你愿意,你可以将返回值放在函数声明的最后,将auto
放在返回值的位置:
auto multiply (int x, int y) -> int;
那么,为什么要这么做呢?为理解这一点,我们先看一个简单的例子:一个带有枚举的类:
class Person { public: enum PersonType { ADULT, CHILD, SENIOR }; void setPersonType (PersonType person_type); PersonType getPersonType (); private: PersonType _person_type; };
我们有一个简单的类,Person
,它有一个类型:一个人要么是成人,要么是小孩,要么是老人。这不用多说,但是,当你定义函数的时候会发生什么呢?
第一个函数,设置器,没什么好说的,你可以直接使用枚举类型PersonType
,没任何问题:
void Person::setPersonType (PersonType person_type) { _person_type = person_type; }
但是,第二个函数就有点问题了。最漂亮的代码却无法通过编译:
// 编译器不知道 PersonType 是什么,因为 PersonType 在 Person 类的外面使用 PersonType Person::getPersonType () { return _person_type; }
你必须这么写:
Person::PersonType Person::getPersonType () { return _person_type; }
这么写才能让返回值正常工作。这不是什么大问题,但是非常容易犯错,并且当使用模板的时候,就更有可能犯错了。
这正是新返回值语法应用的地方。由于返回值出现在函数末端,而不是前部,所以你不需要添加类作用域。当编译器到达返回值的地方时,它已经知道这个函数是Person
类的一部分,所以它知道什么是PersonType
。
auto Person::getPersonType () -> PersonType { return _person_type; }
好吧,这看起来不错,不过它真的那么有用吗?我们能用这个新语法解决前面提出的问题吗?好吧,暂时还不行。我们再新增加一个概念:decltype
。
decltype
decltype
并不是auto
邪恶的一面。auto
允许你用特殊的类型声明变量,decltype
则允许你从一个变量(或任何表达式)导出类型。这是什么意思?
int x = 3; decltype(x) y = x; // 等价于 auto y = x;
你可以为很多表达式使用decltype
,包括函数的返回值类型。嗯,这听起来很熟悉,不是吗?如果我们这么写:
decltype( builder.makeObject() )
就会给出我们makeObject()
函数的返回值类型,允许我们将其指定为makeAndProcessObject
的返回值。我们可以将其与新返回值语法结合来写:
template <typename Builder> auto makeAndProcessObject (const Builder& builder) -> decltype( builder.makeObject() ) { auto val = builder.makeObject(); // 使用 val 干点什么... return val; }
这仅在使用新返回值语法的情形下才可以使用,因为在旧的语法下,我们不能在声明返回类型的地方使用函数参数builder
。但是,结合新语法,函数的所有参数同等对待!
auto
、引用、指针和const
现在肯定会有一个问题,怎么处理引用:
int& foo(); auto bar = foo(); // int& 还是 int?
简单来说,在 C++ 11 中,对于引用,auto
默认使用传值的方式,因此,上面的代码结果是int
。但是,你也可以使用&
修饰符强制使用引用:
int& foo(); auto bar = foo(); // int auto& baz = foo(); // int&
另一方面,如果你有一个指针auto
,它会自动成为一个指针类型:
int* foo(); auto p_bar = foo(); // int*
不过,你也可以显式指明该变量是一个指针:
int* foo(); auto *p_baz = foo(); // int*
同时,如果需要的话,你也可以在处理引用时,为auto
添加const
修饰符:
int& foo(); const auto& baz = foo(); // const int&
或者结合指针:
int* foo(); const int* const_foo(); const auto* p_bar = foo(); // const int* auto p_bar = const_foo(); // const int*
总之,这种感觉是非常自然寻常的,即便在 C++ 的模板中,也遵循同常的规则。
那么,编译器的支持性如何?
就目前而言,GCC 4.4 和 MSVC 10 完全支持上述特性,我已经在产品中使用了这样的代码。这并不是理论上的优点,而是实实在在的。所以,如果你在使用 GCC 时指定-std=c++0x
或者使用 MSVC 10 编译代码,现在你就可以使用这些技术了。如果你使用的是其它编译器,可以在这个页面找到 C++ 11 的编译器支持情况。由于标准已经确认,即将在几周内发布(注:本文发表于 2011 年),现在是时候使用了。