• 第3课 auto类型推导(2)


    第3课 auto类型推导(2)

    一、使用auto的优势

     (一)避免使用未初始化变量

     (二)可简化变量/对象类型的声明

     (三) 在某些场合无法判断出类型时,可用auto自动推导(如lambda表达式)

     (四)可自适应类型,避免隐式类型转换或显式指定类型可能出现的类型错误,增加代码的可移植性。

    【编程实验】优先使用auto

    #include <iostream>
    #include <vector>
    #include <map>
    #include <functional>
    #include <unordered_map>
    #include <boost/type_index.hpp>
    using namespace std;
    using boost::typeindex::type_id_with_cvr;
    
    //辅助类模板,用于打印T的类型
    template <typename T>
    void printType(string s)
    {
        cout << s << " = " << type_id_with_cvr<T>().pretty_name() << endl;
    }
    
    class Widget
    {
        int mArea;
    public:
        bool operator<(const Widget& w) {
            return mArea < w.mArea;
        }
    };
    
    //简化变量/对象类型的声明
    template<typename It>  //It为迭代器类型
    void dwim(It b, It e)  //dwim = do what I mean,做我所想
    {
        while (b != e) {  //遍历从b到e范围内的元素
            //1. 冗余、繁琐的写法
            //typename std::iterator<It>::value_type //萃取迭代器所指的元素类型
            //    currValue = *b;
    
            //2. 使用auto自动推导元素的类型
            auto currValue = *b;
            //...
        }
    }
    
    //在某些场合无法判断出类型时,可用auto自动推导
    class A
    {
    public:
        static int func()
        {
            return 1;
        }
    };
    
    class B
    {
    public:
        static double func()
        {
            return 1.5f;
        }
    };
    
    template<typename T>
    auto test()  //C++14,返回值将根据T::func()类型的不同而不同,但无法事先判断出来!
    {
        return T::func();
    }
    
    int main()
    {
        //1. 避免使用未初始化变量
        //auto x;   //编译失败,x必须被初始化。
    
        //2. 可简化变量/对象类型的声明
        std::map<double, double> resMap;
    
        //2.1 繁琐的写法
        //std::map<double, double>::iterator it = resMap.begin();
        //for(; it != resMap.end(); ++it){
        //    //do something
        //}
    
        //2.2简化写法
        for (auto it = resMap.begin(); it != resMap.end(); ++it) {
        }
    
        //3. 在某些场合无法判断出类型时,可用auto自动推导(如lambda表达式)
        //3.1 C++11写法
        auto lam1 = [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2) {
            return *p1 < *p2;
        };
        //3.2 C++14写法(形参可以用auto推导)
        auto lam2 = [](const auto& p1, const auto& p2)
        {
            return *p1 < *p2;
        };
        printType<decltype(lam1)>("lam1");
        printType<decltype(lam2)>("lam2");
    
    
        //3.2 用std::function来保存lambda(书写同样繁琐!)
        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;
        };
        printType<decltype(func)>("func");
    
        //3.3 无法事先确定类型时,可用auto自动推导。
        cout << test<A>() << endl; //1: 返回值为int
        cout << test<B>() << endl; //1.5 返回值为double
    
        //4. 可自适应类型,增加代码的可重用性和移植性。
        //4.1 莫贪图方便,写出“快捷类型”,如以下的unsigned
        std::vector<int> vec; 
        unsigned sz = vec.size();  //vec.size()返回值的类型为std::vector<int>::size_type与unsigned不完全相同
                                   //unsigned在32位和64位的Windows系统上均是32位的整数。但
                                   //std::vector<int>::size_type在32位和64位Windows分别是32位和64位。
                                   //以上代码从32位移植到64位时,可能出现不一致行为!
        auto sz2 = vec.size();     //自动推导类型:std::vector<int>::size_type,在32位和64位上行为表现一致!
        
        printType<decltype(sz)>("sz");
        printType<decltype(sz2)>("sz2");
    
        //4.2 化解无心之错引发的类型不匹配
        std::unordered_map<std::string, int> m; //unorder_map内部使用std::pair<key,value>存储键值对,且key为const类型
                                                //using value_type      = pair<const _Kty, _Ty>;(参考自VC++2019源码)
        printType<std::unordered_map<string, int>::value_type>("std::unordered_map<string, int>::value_type");
    
        for (const std::pair<std::string, int>& p : m) { //这里不小心将key写成std::string。(正确应为const string)
            //do something                               //由于这个疏忽,尽管p是个引用,但由于类型不匹配,将导致m中的
        }                                                //每个元素产生一个临时std::pair对象并复制给p,从而造成性能损失。
    
        //正确的解法
        for (const auto& p : m) {
            //do something
        }
        return 0;
    }
    /*输出结果:
    lam1 = class <lambda_7300d527c50ad8fd230cc9023161ed22>
    lam2 = class <lambda_e2c5f09f6886e25bc08911f7b03dc529>
    func = class std::function<bool __cdecl(class std::unique_ptr<class Widget,struct std::default_delete<class Widget> > const &,class std::unique_ptr<class Widget,struct std::default_delete<class Widget> > const &)>
    1
    1.5
    sz = unsigned int
    sz2 = unsigned int
    std::unordered_map<string, int>::value_type = struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,int>
    */

    二、auto推导存在的问题

    (一)存在问题

        ①auto声明的变量使用大括号初始化表达式进行初始化时,推导出的类型是initializer_list<T>类型。但当auto用在函数返回值推导时,采用的是模板推导规则而不是auto规则,即无法将大括号初始化表达式自动推导为initializer_list类型。

        ②“隐形”的代理类与auto无法和平共处。(代理类,比如std::vector<bool>::reference,由于std::vector<bool>采用压缩算法来存储bool值,其operator[]返回值不是普通的T&,而是前述的代理类,通过这个类提供的可以实现从代理类到bool值的隐式转换)。应避免写出如下代码auto var1 = “隐形”代理类表达式,而应该显式转换如,auto var1 = static_cast<bool>(“隐形”代理类表达式);

    (二)解决方案:显式指定类型或显式强制类型转换

    【编程实验】auto推导存在的问题

    #include <iostream>
    #include <vector>
    #include <map>
    #include <type_traits>
    
    #include <boost/type_index.hpp>
    using namespace std;
    using boost::typeindex::type_id_with_cvr;
    
    //辅助类模板,用于打印T的类型
    template <typename T>
    void printType(string s)
    {
        cout << s << " = " << type_id_with_cvr<T>().pretty_name() << endl;
    }
    
    class Widget
    {
    public:
    };
    
    //features函数,用于记录Widget对象特性(如第5个比特代表是否具有高优先级)
    //(注意:std::vector<bool>是个特化模板,采用压缩算法来存储bool。如每个
    //比特位用0或1代表一个bool值),其operator[]返回的是std::vector<bool>::reference类型,而不是bool&。
    std::vector<bool> features(const Widget& w)
    {
        return { true, false, true, false, true, false, true};
    }
    
    //根据优先级处理Widget
    void processWidget(const Widget& w, bool priority)
    {
        //do something
    }
    
    double calcEpsilon() //返回容量差
    {
        return 0;
    }
    int main()
    {
        //实验1:std::vector<bool>及隐形代理类
        using ref_t = std::vector<bool>::reference;
        using val_t = decltype(std::vector<bool>()[0]);
    
        cout << std::is_same<ref_t, val_t>::value << endl; //1, 查看std::vector<bool>::reference == operator[] ?
    
        Widget w;
    
        //1.1 正确读取优先级
        bool highPriority = features(w)[5]; //std::vector<bool>::reference到bool的隐式转换
        cout << highPriority << endl;       //ok,这里打印0,表示false;
        processWidget(w, highPriority);     //根据优先级处理Widget
    
        //1.2 错误方式
        auto highPriority2 = features(w)[5]; //返回的是std::vector<bool>::reference,也完全不可能是std::vector<bool>对象
                                             //的第5比特了,因为highPriority2可能存在空悬指针(dangling pointer)。原因如下:
                                             //features()函数调用后,产生一个std::vector<bool>的临时对象(称之temp对象),
                                             //operator[]返回std::vector<bool>::reference对象(称之ref对象)。
                                             //假设std::vector<bool>中的有个保存这些bool压缩形式的比特数据结构,再假设
                                             //std::vector<bool>::reference中有一指针指向该结构,加上5的偏移量就我们要的优先级
                                             //由于auto推导,ref对象会被复制给highPriority2对象,但当features函数结束后,temp对象
                                             //析构,那块比特数据结构也被释放,所以highPrirotity2对象中的那指针会成为空悬指针。
        
        //processWidget(w, highPriority2);   //hightPriority2会隐式转为bool,函数调用成功。但hightPriority2中空悬指针存在,会导致
                                             //未定义行为。
        //1.3 仍使用auto推导的解决方案:
        auto highPriority3 = static_cast<bool>(features(w)[5]); // highPriority3为bool类型。
        processWidget(w, highPriority3);
    
        //实验2:带显式类型转换强制auto推导出所需的类型
        //double calcEpsilon();
        float ep = calcEpsilon(); //从double到float的隐式转换。缺点,隐式转换,可以并非所需。
        auto ep2 = static_cast<float>(calcEpsilon()); //显示转换
        cout << "ep = " << ep << endl;
        cout << "ep2 = " << ep2 << endl;
    
        vector<int> vec = { 1,2,3,4,5,6 };
        double d = 0.5f; //用0.0至1.0之间的值来表示和容器起始处有多远,0.5表示中间位置。
    
        int index = d * vec.size(); //隐式转换,意图不明显。
        auto index2 = static_cast<int>(d * vec.size()); //显示转换
        cout << "index = " << index << endl;
        cout << "index2 = " << index2 << endl;
    }
    /*输出结果:
    1
    0
    ep = 0
    ep2 = 0
    index = 3
    index2 = 3
    */
  • 相关阅读:
    19.模块化
    20.ES7新特性
    22.ES9
    21.ES8新特性
    RabbitMQ:排他性队列(Exclusive Queue)
    为什么要使用消息队列
    收集RabbitMQ的用户
    身份管理提供商:Gigya和PingIdentity
    HTTP协议原理(详细)
    转:Linux下高并发socket最大连接数所受的各种限制
  • 原文地址:https://www.cnblogs.com/5iedu/p/11222053.html
Copyright © 2020-2023  润新知