- 先来定义一个简单的局部变量:
int x; // 糟糕,忘记初始化了。x也许会被初始化为0也许不会,取决于上下文。
- 别在意。再来定义一个用迭代器(Iterator)解引用初始化的局部变量:
template<typename It>
void dwim(It b, It e)
{
while (b != e)
{
// 真的假的?声明一个变量这么麻烦?
typename std::iterator_traits<It>::value_type currValue = *b;
}
}
- 事不过三,再举个例子:声明一个闭包的局部变量。坏了,闭包的类型只有编译器知道。
- 写C++一点都不快乐!(笔者注:原文如此,笑死,加粗表扬)
auto的优势
- 在C++11,由于
auto
的出现,以上这些问题都不复存在。auto
由initializer推断类型,因此不会出现变量未初始化问题。
auto x1; // error!
auto x2 = 0; // well-defined.
template<typename It>
void dwim(It b, It e)
{
while (b != e)
{
auto currValue = *b; // 舒服了
}
}
// 可以表示只由编译器知道的类型
auto derefUPLess =
[](const std::unique_ptr<Widget>& p1,
const std::unique_ptr<Widget>& p2)
{ return *p1 < *p2; };
- 在C++14中,lambda表达式的参数也可以使用
auto
:
auto derefUPLess =
[](const auto& p1,
const auto& p2)
{ return *p1 < *p2; };
- 你对第三个例子也许会想到使用
std::function
。概念:C++11对函数指针概念的泛化,可以指向任何可调用的(callable)对象(注:如重载了operator()的类,俗称functor)。声明时在模板中给出指向函数的签名(signature):
// 不使用auto的版本
std::function<bool(const std::unique_ptr<Widget>&,
const std::unique_ptr<Widget>&)>
derefUPLess = [](const std::unique_ptr<Widget>& p1,
const std::unique_ptr<Widget>& p2)
{ return *p1 < *p2; };
// tmd, C++怎么会变成这个样子.jpg
-
且不论语法上的繁杂性,使用
std::function
和auto
是不同的。auto
声明的闭包变量其类型就是自身,也只占用闭包需求的内存量。而std::function
创建的是一个持有闭包对象的std::function
对象,这通常会占用更多的内存,而且调用速度几乎肯定会更慢。本场比赛中,auto
完胜。 -
auto
的优势还不只如此。以下程序是你可能见过(或者写过的):
std::vector<int> v;
unsigned sz = v.size();
v.size()
返回值的正式形式为std::vector<int>::size_type
,然而很少开发者真正了解这一点,而往往认为用unsigned
足够了。在32位机器上,两者大小相同;然而在64位机器上,前者是64位而后者是32位!
- 注:相信更多人(包括我)更习惯使用
size_t
的写法,这是没有问题的(见下图)。原书中没有提到这一点。
- 显式类型声明可能带来另一种隐患:
std::unordered_map<std::string, int> m;
m.insert(std::make_pair("123", 1));
m.insert(std::make_pair("234", 2));
for (const std::pair<std::string, int>& p : m)
{
... // do something with p
}
- 一眼看上去没什么问题不是吗?但是你可能已经忘记了一个事实:
std::unordered_map
的键部分是const
,因此哈希表中的元素不是std::pair<std::string, int>
(声明的p
的类型),而是std::pair<const std::string, int>
!于是,编译器会想办法将后者转换为前者,并且还真的有一种方案(因此不会报错):把后者复制到一个临时对象,再降其引用赋给p
。这绝对不是你想要的效果。用auto
可以解决这一切问题:
auto的不足
- Item 2讨论了有时
auto
推断出的类型可能不是你想要的一些情况。 auto
可能会带来代码可读性上的问题。IDE的类型提示和命名风格良好的变量名称可以缓解这一问题。- 归根结底,
auto
只是你的一个可选工具。因此还是要根据实际场景合适地利用其强大功能。
总结
auto
变量必须被初始化,几乎能解决所有移植性和效率上的问题,还能简化代码重构的过程,同时能减少你的打字量。auto
类型的变量容易掉入Item 2和6所描述的坑中。