深入理解C++11(一)

深入理解C++11(一)

类型推导
C++11引入了auto和decltype关键字实现类型推导,通过这两个关键字不仅能方便地获取复杂的类型,而且还能简化书写,提高编码效率。
auto类型推导

auto x = 5;// OK: x 是
auto pi = new auto(1); // OK: pi 被推导为 int*
const auto *v = &x, u = 6; // OK: v 是const int* 类型,u 是const int 类型
auto int r; // error: auto 不再表示存储类型指示符
auto s; // error: auto 无法推导出 s 的类型

在上面的代码示例中:字面量5是一个const int类型,变量x将被推导为int
类型(const被丢弃,后面说明),并被初始化为5;pi的推导说明auto还可以
用于new操作符。在例子中,new操作符后面的auto(1)被推导为int(1),
因此pi的类型是int*;接着,&x的类型为int*,推导出const auto*中的auto应
该是int,于是v被推导为const int*,而u则被推导为const int

注意:虽然经过前面const auto*v=&x的推导,auto的类型可以确定为int了,但是u仍然必须要写后面的“=6”,否则编译器不予通过。u的初始化不能使编译器推导产生二义性。例如,把u的初始化改成“u=6.0”,编译器将会报错。

考虑到auto在C++中使用的较少,在C++11标准中,auto关键字不再表示存储类型指示符(storage-class-specifiers,如上文提到的static,以及register、mutable等),而是改成了一个类型指示符(type-specifier),用来提示编译器对此类型的变量做类型的自动推导。

auto的限制

代码清单1-1 auto使用受限的示例
void func(auto a = 1) {}  // error: auto 不能用于函数参数
struct Foo
{
auto var1_ = 0;  // error: auto 不能用于非静态成员变
static const auto var2_ = 0;  // OK: var2_ -> static const int
};
template <typename T>
struct Bar {};
int main(void)
{
int arr[10] = {0};
auto aa= arr;  // OK: aa -> int *
auto rr[10] = arr;  // error: auto 无法定义数组**
Bar<int> bar;
Bar<auto> bb = bar;
return 0;
}

在Foo中,auto仅能用于推导static const的整型或者枚举成员(因为其他静
态类型在C++标准中无法就地初始化 [3] ),虽然C++11中可以接受非静态成
员变量的就地初始化,但却不支持auto类型非静态成员变量的初始化。

什么时候用auto
在C++11之前,定义了一个stl容器以后,在遍历的时候常常会写这样的代码:

#include <map>
int main(void)
{
std::map<double, double> resultMap;
// ...
std::map<double,double>::iterator it = resultMap.begin();
for(; it != resultMap.end(); ++it)
{
// do something
}
return 0;
}

观察上面的迭代器(iterator)变量it的定义过程,总感觉有点憋屈。其实
通过resultMap.begin(),已经能够知道it的具体类型了,却非要书写上长长
的类型定义才能通过编译。

来看看使用了auto以后的写法:

#include <map>
int main(void)
{
std::map<double, double> resultMap;
// ...
for(auto it = resultMap.begin(); it != resultMap.end(); ++it)
{
// do something
}
return 0;
}

再次观察it的定义过程,是不是感到清爽了很多?
再看一个例子,在一个unordered_multimap中查找一个范围,代码如下:

#include <map>
int main(void)
{
std::unordered_multimap<int, int>resultMap;
// ...
std::pair<std::unordered_multimap<int,int>::iterator,
std::unordered_multimap<int, int>::iterator>
range = resultMap.equal_range(key);
return 0;
}

这个equal_range返回的类型声明显得烦琐而冗长,而且实际上并不关心这里的具体类型(大概知道是一个std::pair就够了)。这时,通过auto就能极大的简化书写,省去推导具体类型的过程:

#include <map>
int main(void)
{
std::unordered_multimap<int, int> map;
// ...
auto range = map.equal_range(key);
return 0;
}

另外,在很多情况下我们是无法知道变量应该被定义成什么类型的,比如,如代码清单1-2所示的例子。
代码清单1-2 auto简化函数定义的示例:

class Foo
{
public:
static int get(void)
{return 0;
}
};
class Bar
{
public:
static const char* get(void)
{
return "0";
}
};
template <class A>
void func(void)
{
auto val = A::get();
// ...
}
int main(void)
{
func<Foo>();
func<Bar>();
return 0;
}

在这个例子里,我们希望定义一个泛型函数func,对所有具有静态get方
法的类型A,在得到get的结果后做统一的后续处理。若不使用auto,就不得不
对func再增加一个模板参数,并在外部调用时手动指定get的返回值类型。

注意 :auto是一个很强大的工具,但任何工具都有它的两面性。不加选择地随意使用auto,会带来代码可读性和维护性的严重下降。因此,在使用auto的时候,一定要权衡好它带来的“价值”和相应的“损失”。
decltype关键字
1.获知表达式的类型
上一节所讲的auto,用于通过一个表达式在编译时确定待定义的变量类型,auto所修饰的变量必须被初始化,编译器需要通过初始化来确定auto所代表的类型,即必须要定义变量。若仅希望得到类型,而不需要(或不能)定义变量的时候应该怎么办呢?

C++11新增了decltype关键字,用来在编译时推导出一个表达式的类型。它的语法格式如下:

decltype(exp)

从格式上来看,decltype很像sizeof——用来推导表达式类型大小的操作
符。类似于sizeof,decltype的推导过程是在编译期完成的,并且不会真正计算
表达式的值。

那么怎样使用decltype来得到表达式的类型呢?让我们来看一组例子:

int x = 0;
decltype(x) y = 1;  // y -> int
decltype(x + y) z = 0;  // z -> int
const int& i = x;
decltype(i) j = y;  // j -> const int &
const decltype(z) * p = &z;  // *p -> const int, p -> const int *
decltype(z) * pi = &z;  // *pi -> int, pi -> int *
decltype(pi)* pp = &pi;  // *pp -> int *, pp -> int * *

y和z的结果表明decltype可以根据表达式直接推导出它的类型本身。这个功能和上一节的auto很像,但又有所不同。auto只能根据变量的初始化表达式推导出变量应该具有的类型。若想要通过某个表达式得到类型,但不希望新变量和这个表达式具有同样的值,此时auto就显得不适用了。

j的结果表明decltype通过表达式得到的类型,可以保留住表达式的引用及const限定符。实际上,对于一般的标记符表达式(id-expression),decltype将精确地推导出表达式定义本身的类型,不会像auto那样在某些情况下舍弃掉引用和cv限定符。

2.decltype的推导规则
从上面一节内容来看,decltype的使用是比较简单的。但在简单的使用方法之后,也隐藏了不少细节。

我们先来看看decltype(exp)的推导规则 :

·推导规则1,exp是标识符、类访问表达式,decltype(exp)和exp的类型一致。

·推导规则2,exp是函数调用,decltype(exp)和返回值的类型一致。

·推导规则3,其他情况,若exp是一个左值,则decltype(exp)是exp类型
的左值引用,否则和exp类型一致。

3.decltype的实际应用
decltype的应用多出现在泛型编程中。考虑代码清单1-5的场景。
代码清单1-5 泛型类型定义可能存在问题的示例

#include <vector>
template <class ContainerT>
class Foo
{
typename ContainerT::iterator it_; // 类型定义可能有问题
public:
void func(ContainerT& container)
{
it_ = container.begin();
}
// ...
};
int main(void)
{
typedef const std::vector<int> container_t;
container_t arr;
Foo<container_t> foo;
foo.func(arr);
return 0;
}

单独看类Foo中的it_成员定义,很难看出会有什么错误,但在使用时,若
上下文要求传入一个const容器类型,编译器马上会弹出一大堆错误信息。
原因就在于,ContainerT::iterator并不能包括所有的迭代器类型,当
ContainerT是一个const类型时,应当使用const_iterator

3.返回类型后置语法——auto和decltype的结合使用
考虑下面这个场景:

template <typename R, typename T, typename U>
R add(T t, U u)
{
return t+u;
} 
int a=1; 
float b=2.0;
auto c = add<decltype(a + b)>(a, b);

我们并不关心a+b的类型是什么,因此,只需要通过decltype(a+b)直接
得到返回值类型即可。但是像上面这样使用十分不方便,因为外部其实并不
知道参数之间应该如何运算,只有add函数才知道返回值应当如何推导。那
么,在add函数的定义上能不能直接通过decltype拿到返回值呢?
template <typename T, typename U>
decltype(t + u) add(T t, U u) // error: t、
u尚未定义
{
return t + u;
}

当然,直接像上面这样写是编译不过的。因为t、u在参数列表中,而
C++的返回值是前置语法,在返回值定义的时候参数变量还不存在。

可行的写法如下:

template <typename T, typename U>
decltype(T() + U()) add(T t, U u)
{
return t + u;
}

考虑到T、U可能是没有无参构造函数的类,正确的写法应该是这样:

template <typename T, typename U>
decltype((*(T*)0) + (*(U*)0)) add(T t, U u)
{
return t + u;
}

虽然成功地使用decltype完成了返回值的推导,但写法过于晦涩,会大大
增加decltype在返回值类型推导上的使用难度并降低代码的可读性

返回类型后置语法是通过auto和decltype结合起来使用的。上面的add函数,使用新的语法可以写成:

template <typename T, typename U>
auto add(T t, U u) -> decltype(t + u)
{
return t + u;
}

为了进一步说明这个语法,再看另一个例子:

int& foo(int& i);
float foo(float& f);
template <typename T>
auto func(T& val) -> decltype(foo(val))
{
return foo(val);
}

如果说前一个例子中的add使用C++98/03的返回值写法还勉强可以完成,
那么这个例子对于C++98/03而言就是不可能完成的任务了。
在这个例子中,使用decltype结合返回值后置语法很容易推导出了
foo(val)可能出现的返回值类型,并将其用到了func上

返回值类型后置语法,是为了解决函数返回值类型依赖于参数而导致难以确定返回值类型的问题。有了这种语法以后,对返回值类型的推导就可以用清晰的方式(直接通过参数做运算)描述出来,而不需要像C++98/03那样使用晦涩难懂的写法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值