对于 C++11 的 auto 关键字,感觉使用的过程并没有做到得心应手,知其然不知其所以然。
所以系统学习并总结一下,我们什么时候应该使用 auto 来定义变量。
1)当然是少写 N 多的字符
// 获取迭代器的类型
typename std::iterator_traits<It>::value_type currValue = *b; //C++98写法
auto currValue = *b; //C++11写法,可以看到简练很多2)避免未初始化的变量
int x1; // 未初始化
auto x2; // 编译错误!使用auto必须初始化
auto x3 = 0; // 正确!3)直接持有closure(闭包)
// 类比较函数(函数类型【这里应该特指返回值类型,因为参数类型是知道的】仅编译器知道)
auto derefUPLess = [](const std::unique_ptr<Widget>& p1,
const std::unique_ptr<Widget>& p2) {
return *p1 < *p2;
};
// c++14 auto可以用于函数参数,写法将更加简洁
auto derefUPLess = [](const auto& p1,
const auto& p2) {
return *p1 < *p2;
};《Effective Modern C++》中有提到,auto 声明比std::function性能更好(后者会涉及到一些复制操作等,未深究)。
4)避免类型不匹配问题
例1
std::vector<int> v;
…
unsigned sz = v.size();v.size()的返回值是std::vector<int>::size_type,从官网上的说明可以看到它就是size_t(unsigned integer types)。对于 Intel 架构平台,各数据类型实际所占字节如下(引自Integer sizes in C on 32 bit and 64 bit Linux):
| type executable[1] | 32 bit | 64 bit |
|---|---|---|
| short int | 16 bit | 16 bit |
| int | 32 bit | 32 bit |
| long int | 32 bit | 64 bit |
| long long int | 64 bit | 64 bit |
| size_t | 32 bit | 64 bit |
| void* [2] | 32 bit | 64 bit |
可以看到,对于 64 位平台,[unsigned ] int与size_t不等,所以上述代码在 64 位平台下可能出现“类型截断”。这时候,使用 auto 就是更好的选择了:
auto sz = v.size(); // sz's type is std::vector<int>::size_type
例2
std::unordered_map<std::string, int> m;
…
for (const std::pair<std::string, int>& p : m)
{
… // do something with p
}这里有一个陷阱需要注意,就是 map 的 key 是不可变的(map keys are semantically immutable),所以所有对于 map key 的直接操作(譬如解引用迭代器,产生value_type),都会对 key 加上const属性(参考What is difference between const and non const key?)。
也就是说,上面的例子中,map 中的std::pair类型是std::pair<const std::string, int>,而不是std::pair<std::string, int>。 所以将 p 声明为std::pair<std::string, int>后,编译器会尝试将std::pair<std::string, int>转换为std::pair<const std::string, int>。这个过程是这么实现的:对每个 p 想要绑定的 m 中的一员,先将其复制到一个临时对象,然后将 p 绑定到这个临时变量上。在每次循环最后,这个临时对象会被销毁。
上面的过程,其实与你的“初心”是不符的,因为你仅仅只是想将 p 绑定到 m 的所有元素而已。
当然,这时候使用 auto 就能避免这样的“类型不匹配”了:
for (const auto& p : m)
{
… // as before
}除去类型正确、字符更少的好处,这里还有一点值得提出,就是获取 p 的地址的时候,可以正确的拿到 m 里元素的地址。在不使用 auto 的写法中,获取 p 的地址拿到的是临时对象的地址,而这个临时对象是在循环结束前会被销毁的。
5)利于代码重构
想像一个场景:你定义的一个函数返回int类型,然而随着你的软件使用,int已经不能满足你的需求,你需要将返回值改为long。此时,假如在获取返回值的地方,你是用的是 auto,那么你只需要在函数声明和函数定义处修改。假如你是用的是精确类型定义,那你就需要找出每个调用了此函数并用int类型获取返回值的地方,并全部修改它们。
总结
auto 可以显著减少输入的字符数,可以避免未初始化变量的出现(因为定义 auto 变量必须初始化),也可以防止因为类型不匹配导致的程序错误(例1)及性能问题(例2),对代码重构的工作量也有一定的帮助。
当然,也存在一些不能使用 auto 的场景,下回分解。
BY:HW-LH






































苏公安网备 32011402010933号