Effective Modern C++——auto

文章目录

  • auto
    • [优先选用 auto,而非显式型别声明](#优先选用 auto,而非显式型别声明)
    • [当 auto 推导的型别不符合要求时,使用带显式型别的初始化物习惯用法](#当 auto 推导的型别不符合要求时,使用带显式型别的初始化物习惯用法)

auto

优先选用 auto,而非显式型别声明

用 auto 声明的变量,其型别推导都推导自其初始化物,所以它们必须初始化。

当 auto 声明变量却未初始化时,会编译错误。

用 auto 声明变量时可以简化代码。

cpp 复制代码
template<typename It>
//dwim(做我想做,do what i mean)算法
//应用范围是从b到e的所有元素
void dwim(It b,It e){
    while(b!=e){
        typename std::iterator_traits<It>::value_type
        currValue=*b;
        //...
    }
}

//用auto简化
template<typename It>
//dwim(做我想做,do what i mean)算法
//应用范围是从b到e的所有元素
void dwim(It b,It e){
    while(b!=e){
        auto currValue=*b;
        //...
    }
}
cpp 复制代码
//不推荐
std::function<bool(int)> func = [](int x) { return x > 0; };

//推荐
auto func = [](int x) { return x > 0; };

用 auto 可以优化闭包声明。

用 auto 声明闭包比 function 性能更优越。

特性 auto 声明闭包 std::function 声明
内存 类型和闭包类型一样,仅占用闭包捕获变量所需的空间 类型为 function,存储 function 实例,内存对齐 function 类型,通常会有堆内存分配
速度 编译器可以进行内联优化 存在间接调用,难以内联
类型匹配 绝对精确匹配 可能由于类型不完全匹配导致隐式转换

用 auto 声明变量可以避免型别捷径。

cpp 复制代码
vector<int>v;
//v.size()返回std::vector<int>::size_type类型,规定成一个无符号整数
unsigned sz=v.size();

在 32 位 windows 上,unsigned 和std::vector::size_type 尺寸是**一样**,但是在 64 位 windows 上,**前者为 32 位,后者为 64 位**,在 32 位的程序迁移到 64 位 Windows 上会出现异常,并且难以调试。

用 auto 则尺寸一致:

cpp 复制代码
auto sz=v.size();//sz的型别为std::vector<int>::size_type
cpp 复制代码
std::unordered_map<std::string,int>m;
for(const std::pair<std::string,int>&p:m){
    //...
}

看似没什么问题,实际上 std::unordered_map 的**键值部分是 const 只读的,即类型型别为 std::pair<const std::string,int>,而当该类型赋值到类型为 std::pairstd::string,int& 的变量时,它并不会随我们期待的那样: p 为 m 容器中值的引用**。而是先对 m 中的** 每个对象都做一次复制操作**,形成一个 p 想要绑定的型别的临时对象,然后把 p 这个引** 用绑定到该临时对象**。在**每次循环迭代结束时该临时对象会被析构一次**。

这样下来,不仅没有成功引用到 m 中的对象,多次复制和析构也会导致不必要的性能开销。而使用 auto 可以保证类型正确,消除影响。

cpp 复制代码
std::unordered_map<std::string,int>m;
for(const std::pair<std::string,int>&p:m){
    //...
}

当 auto 推导的型别不符合要求时,使用带显式型别的初始化物习惯用法

cpp 复制代码
#include<vector>

class Widget {
public:
    Widget(int id) : id(id) {}
private:
    int id;
};

std::vector<bool> features(const Widget&w){
    //...
}

void processWidget(const Widget&w,const bool isHigh){
    //...
}

int main(){
    Widget w(0);

    bool highPriority1=features(w)[5];
    processWidget(w,highPriority1);

    //类型推导为std::vector<bool>::reference
    auto highPriority2=features(w)[5];
    //未定义行为
    processWidget(w,highPriority2);
    
    return 0;
}

为什么用 auto 推导出的 highPriority2 会出现未定义行为?

原因在于 std::vector[] 的返回值并不是我们对于该容器其他型别一样所期待的 bool&(C++中禁止返回比特的引用),而是**std::vector::reference**。该类型能够实现在所有能用 bool&的地方的保证它们也能用,因为该类型做了一个向** bool 的隐式型别转换**。

std::vector::reference 是一个代理类,智能指针也是代理类,区别在于后者是显式使用,而前者趋于隐式使用。我们知道的是智能指针通过内置一个指针指向特定对象来代理该对象,std::vector::reference 也是一样。

⚠️但是在调用 features 时会返回一个 std::vector型别的临时对象 temp,而highPriority2 则代理的是该临时对象 temp,在表达式结束时,temp 发生析构,则highPriority2 内置的指针为空悬指针,所以会导致未定义行为。

📢**解决方法**:**使用带显式型别的初始化物习惯用法**(强制进行另一次型别转换),这样就能使用 auto 推导接受。

cpp 复制代码
auto highPriority2=static_cast<bool>(features(w)[5]);

这种习惯用法的应用并不限于会产生代理型别别的初始化物。它同样可以应用于你想要强调你意在创建一个型别有异于初始化表达式型别的变量的场合。

cpp 复制代码
//假设有一个计算某容差值的函数
double calcEpsilon();//返回容量差

float ep=calcEpsilon();//隐式转换
auto ep=static_cast<float>(calcEpsion());//强制转换
相关推荐
lbb 小魔仙20 分钟前
基于Python构建RAG(检索增强生成)系统:从原理到企业级实战
开发语言·python
代码的小搬运工41 分钟前
UITableView
开发语言·ui·ios·objective-c
刚子编程43 分钟前
C# Join 深度解析:参数顺序、多表关联与空值处理最佳实践
开发语言·c#·最佳实践·join·多表关联·空值处理
AbandonForce44 分钟前
哈希表(HashTable,散列表)个人理解
开发语言·数据结构·c++·散列表
代码中介商1 小时前
栈结构完全指南:顺序栈实现精讲
c语言·开发语言·数据结构
平凡但不平庸的码农1 小时前
Go 错误处理详解
开发语言·后端·golang
样例过了就是过了1 小时前
LeetCode热题100 编辑距离
数据结构·c++·算法·leetcode·动态规划
z200509301 小时前
C++中位图和布隆过滤器的一些面试题
开发语言·c++
Bat U1 小时前
JavaEE|文件操作和IO
java·开发语言
脉动数据行情2 小时前
Python 实现融通金行情数据对接(实时推送 + K 线 + 产品列表)
开发语言·python