C++11 auto and decltype 以及新的函数声明语法

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 
}

并且编译器会告诉你它不知道abdecltype参数中是什么。那是因为它们只是由参数列表声明。

您可以使用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;
}




参考文章:

C++ 11 改进的类型推断:auto,decltype 以及新的函数声明语法

函数标题中的箭头运算符

发布了119 篇原创文章 · 获赞 152 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/weixin_40539125/article/details/103539453