当前位置: 代码迷 >> 综合 >> Effective Modern C++ Item5 优先选用auto,而非显示型别声明
  详细解决方案

Effective Modern C++ Item5 优先选用auto,而非显示型别声明

热度:98   发布时间:2024-01-28 13:07:51.0

auto的优势可不仅仅是让你少打几个类型字符,在有些时候,可以防止手动指定型别带来的错误和性能问题。但这武器也有弊端,在没有IDE的时候,过度auto使用会影响代码的可读性,甚至在极端情况,会有一些意想不到的事情发生。

优势1:auto语句定义变量在无初始化值的时候无法通过编译

int x1;             //有潜在的为初始化风险
auto x2;            //编译错误!必须要初始化
auto x3 = 0;        //没有问题,x的值有合适的定义

优势2:auto表示可以少打几个字,看起来更加简洁

template<typename It>
void dwim(It b, It e)
{while (b != e) {typename std::iterator_traits<It>::value_type currValue = *b;auto currValue = *b;            //采用auto的写法...}
}

优势3:闭包的型别类型只能用auto表示

// C++11版本
auto derefUPLess =[](const std::unique_ptr<Widget>& p1,const std::unique_ptr<Widget>& p2){return *p1 < *p2;};
// C++14版本,这个版本可用于任何类似指针之物的比较
auto derefUPLess =[](const auto& p1,const auto& p2){return *p1 < *p2;};

也许有人说,用std::function可以表示闭包的型别,这个优势有问题,那么对于这一点,细究一下。其实对于下面的写法,还是有区别的:

// auto版本
auto derefUPLess =[](const std::unique_ptr<Widget>& p1,const std::unique_ptr<Widget>& p2){return *p1 < *p2;};
// std::function版本
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;};

撇开function版本的啰嗦不说,性能上同样存在区别。

  • auto声明的、存储一个闭包的变量,和这个闭包类型相同,所以消耗的存储空间一致。而std::function声明的、存储一个闭包的变量是std::function的一个实例,不管给定的签名如何,它都占有固定尺寸的内存。

  • 更糟糕的情况是,上述固定尺寸的内存并不一定够用,在不够的情况下,std::function构造函数会分配堆上的内存来存储该闭包。

  • 再考虑到编译器实现细节会限制内联产生间接函数调用,此时std::function开销会变得更大。

综上对比,在闭包声明的时候,auto完胜std::function的。

优势4:避免不小心造成的类型隐身转换

用例子说明:

std::vector<int> v;
...
unsigned sz = v.size();
auto sz = v.size();

上述两种定义中,sz的型别其实是不同的,v.size()在不同平台下,会产生不同的类型,他的真正型别为std::vector<int>::size_type。在32位的系统里是第一种写法是一致的,但是64位系统里,则会出现类型缩窄。但使用auto的写法则能避免这样的问题。如果这种区别不足以让你觉得满足,那么看看下面的例子:

std::unordered_map<std::string, int> m;
...for (const std::pair<std::string, int>& p : m)
{...                 //在p上实施某些操作
}

这段代码看起来合理,但是会有极大隐藏的性能损失,std::unordered_map的键值部分是const,所以哈希表中的std::pair的型别不是std::pair<std::string, int>而是std::pair<const std::string, int>。所以编译器会想办法让类型匹配起来,从而将每一个遍历的对象都复制一遍来满足类型匹配。而这个过程无疑调用了数不清次数的拷贝函数和析构函数。

std::unordered_map<std::string, int> m;
...for (const auto& p : m)
{...                 //在p上实施某些操作
}

而这样写,一切都没问题了,而且代码也更容易理解了。

劣势 1:auto在某些特殊环境下,推导出来的结果不正常

详见 item2item6

劣势 2:auto使用过多会导致代码可读性降低

满篇的auto必定让你看不清变量的类型

要点速记
1. auto变量必须初始化,基本上对会导致兼容性和效率问题的型别不匹配现象免疫,还可以简化重构流程,通常也比显示指定型别少打一些字。
2. auto型别变量都有item2item6的毛病。
  相关解决方案