第二章:auto
《Effective Modern C++》阅读笔记(二)
条款5:优先考虑auto
而非显式声明
auto
可以简化变量声明,例如:
template<typename It>
void dwim(It b,It e)
{
while (b != e) {
auto currValue = *b; // currValue的类型为std::iterator_traits<It>::value_type
…
}
}
再例如:
auto sz = v.size(); // sz的类型是std::vector<int>::size_type
auto
甚至能表示一些只有编译器才知道的类型:
auto derefUPLess =
[](const std::unique_ptr<Widget> &p1, // 用于std::unique_ptr指向的Widget类型的
const std::unique_ptr<Widget> &p2)
{ return *p1 < *p2; }; // 比较函数
如果使用C++14可以进一步简化为:
auto derefLess = // C++14版本
[](const auto& p1, // 被任何像指针一样的东西指向的值的比较函数
const auto& p2)
{ return *p1 < *p2; };
这里面derefLess
变量的类型是一个不可命名的,匿名函数对象类型,只有编译器可见。
当然这里的闭包可以用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; };
但是这样一方面会造成代码冗长,同时也会带来额外的内存空间消耗(std::function
对象本身占用空间)。
auto
的使用还可能避免可能的额外的内存复制开销。例如遍历一个哈希表m
,如果不使用auto
可能会写成如下:
std::unordered_map<std::string, int> m;
…
for(const std::pair<std::string, int>& p : m)
{
… // 用p做一些事
}
但是实际上m
中每个元素的类型为std::pair<const std::string, int>
,如果按照上述的写法则会发生std::pair<const std::string, int>
到std::pair<std::string, int>
的隐式转换导致的复制。另外如果在遍历时使用了p
的内存地址,则这个地址是指向复制后的临时变量,如果在该临时变量析构后访问可能会导致潜在的内存错误。
条款6:如果auto
推导不符合预期,使用显式型别初始化
auto
在一些场景下使用可能会导致潜在的风险,例如:
std::vector<bool> features(const Widget& w); // 这个是函数
Widget w;
…
auto highPriority = features(w)[5];
…
processWidget(w, highPriority); // 未定义行为!
上例中highPriority
变量的类型为std::vector<bool>::reference
,std::vector<bool>::reference
相对于其他的std::vector<T>::reference
比较特殊,其引用的是该元素所在的字对象,例如本例中features(w)[5]
返回的是函数features
返回值创建的临时std::vector<bool>
对象中第五个bit
所在字的引用,而highPriority
也是指向该临时对象的引用,但是此时该临时变量已经析构,所以会导致未定义的行为。
这里应该进行显式转换:
std::vector<bool> features(const Widget& w); // 这个是函数
Widget w;
…
bool highPriority = features(w)[5];
…
processWidget(w, highPriority);