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
和新返回值语法的乐趣
在C++ 11中,函数声明有两种语法:
return-type identifier ( argument-declarations ... )
auto identifier ( argument-declarations ... )-> return_type
它们是等价的。现在当它们相同时,你为什么要使用后者呢?为理解这一点,我们先看一个简单的例子:一个带有枚举的类:
class Person
{
public:
enum PersonType { ADULT, CHILD, SENIOR };
void setPersonType (PersonType person_type);
PersonType getPersonType ();
private:
PersonType _person_type;
};
我们有一个简单的类,Person
,它有一个类型:一个人要么是成人,要么是小孩,要么是老人。这不用多说,但是,当你定义函数的时候会发生什么呢?
你必须这样写:
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
。但是,结合新语法,函数的所有参数同等对待。
可能你还是不知道什么地方运用,再看个具体点的实例:
template <typename T1, typename T2>
decltype(a + b) compose(T1 a, T2 b){
// some code
}
并且编译器会告诉你它不知道a
和b
在decltype
参数中是什么。那是因为它们只是由参数列表声明。
您可以使用declval
和已声明的模板参数轻松解决此问题。
template <typename T1, typename T2>
decltype(std::declval<T1>() + std::declval<T2>())
compose(T1 a, T2 b){
// some code
}
除了它现在变得非常冗长。因此,提出并实现了备用声明语法,现在您可以编写
template <typename T1, typename T2>
auto compose(T1 a, T2 b) -> decltype(a + b){
//some code
}
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 年),现在是时候使用了。
附测试代码:
#include <iostream>
using namespace std;
class Person
{
public:
enum PersonType { ADULT, CHILD, SENIOR };
void setPersonType(PersonType person_type);
PersonType getPersonType();
private:
PersonType _person_type;
};
//第一个函数,设置器,没什么好说的,你可以直接使用枚举类型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;
//}
auto Person::getPersonType() -> PersonType
{
return _person_type;
}
//error a\b can`t find
//template <typename T1, typename T2>
//decltype(a + b) compose(T1 a, T2 b) {
//
// return a + b;;
//}
//template <typename T1, typename T2>
//decltype(std::declval<T1>() + std::declval<T2>())
//compose(T1 a, T2 b)
//{
// return a + b;
//}
template <typename T1, typename T2>
auto compose(T1 a, T2 b) -> decltype(a + b) {
return a + b;
}
int main() {
int i=0, j=10;
cout << compose(i,j)<< endl;
return 0;
}
参考文章: