条款5:优先选用auto,而非显示类型声明
1. 摆脱繁杂的类型声明
2.避免踩坑
1)使用auto与使用std::function声明闭包的对比
//使用std::function声明一个闭包
std::function<bool(const std::unique_ptr<Widget>&,
const std::unique_ptr<Widget>&)>
func = [](const std::unique_ptr<Widget>&p1,
const std::unique_ptr<Widget>&p2)
{return *p1 < *p2};
//使用auto声明一个闭包
auto func = [](const std::unique_ptr<Widget>&p1,
const std::unique_ptr<Widget>&p2)
{return *p1 < *p2};
使用auto除了声明上的方便,还有以下区别:使用auto声明的,存储该闭包的变量和该闭包是同一个类型,从而它要求的内存和闭包相同。而使用std::function声明的,存储闭包的变量只是std::function的一个实例,所以它具有固定的std::function类型的大小,而这个大小对闭包来说不一定够用,这种情况下,std::function的构造函数就会使用堆内存来使用该闭包。再有,编译器的实现细节一般都会限制内联,这种情况下会有函数调用的开销。
总结:std::function这种写法比auto来的 1.啰嗦 2.可能导致内存耗尽异常
2)类型窄化
考虑如下代码
std::vector<int> v;
...
unsigned size = v.size();
标准规定:v.size()
返回应该是std::vector<int>::size_type
,类型的不同可能会导致一些难以预料的错误。如在32位windows上,std::vector<int>::size_type
与unsigned
尺寸是一样的,而在64位windows上,unsigned
是32位,而std::vector<int>::size_type
是64位,这样可能会导致一些很难排查的异常。
而使用auto则会有效避免这个可能的错误
3)unordered_map
考虑如下代码:
std::unordered_map<std::string, int> m;
//some work to do
for(const std::pair<std::string. int> &p:m){
//some work to do
}
想要发现上述代码的异常,就要记住std::unordered_map的键值部分是const,也就是m中存储的是std::pair<const std::string,int>
,而不是std::pair<std::string, int>
。上面的声明错误会使编译器想方设法将std::pair<const std::string,int>
转化为std::pair<std::string, int>
,它找到的方法是对m中的每个对象都做一次复制操作,把m中的元素复制到一个临时对象上,再把p绑定到这个临时对象上,在循环的每次迭代结束时,会再调用析构函数。除了调用构造函数和析构函数的开销之外,另一个可能引起灾难性后果的是对p取址,那么只是对临时对象取址,这可能会导致程序直接崩溃!
条款6:auto可能导致的不符合预期行为
auto返回隐形的代理类型导致不符合预期
考虑如下代码:
vector<bool> features();
auto ret = features()[5];
这里ret的类型并不是预期的bool类型,而是一个对用户隐形的std::vector<bool>::reference
类型。这是因为vector<bool>
的operator []
的返回值并不是bool,而是一个代理类型:std::vector<bool>::reference
,而这可能会引起错误,严重甚至可能会产生空悬指针导致程序崩溃。
详细原因请参见Effective Modern C++ P47
再考虑如下代码:
vector<bool> features();
bool ret = features()[5];
上面的代码会使std::vector<bool>::reference
隐示转化为bool对象,从而使后面的调用符和预期。
一个普遍的规律是:"隐形代理类"和auto无法和平共处。