int x;
- 别在意。再来定义一个用迭代器(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;
auto x2 = 0;
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):
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; };
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)
{
...
}
- 一眼看上去没什么问题不是吗?但是你可能已经忘记了一个事实:
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所描述的坑中。
|