• 《Effective C++中文版,第三版》读书笔记7


    条款41: 了解隐式接口和编译期多态

    隐式接口:

    ​ 仅仅由一组有效表达式构成,表达式自身可能看起来很复杂,但它们要求的约束条件一般而言相当直接而明确。

    显式接口:

    ​ 通常由函数的签名式(也就是函数名称、参数类型、返回类型)构成

    ​ 在源码中明确可见。

    #include 
    
    // 个人感觉隐式和显式接口只是站在了不同的角度看一个类的接口。
    // 站在类的角度:
    // 类中定义的接口是显示的
    
    // 站在template 参数的这个角色的角度而言:接口是隐式的,就是个表达式。
    // (换个说法:这个隐式接口其实就是隐藏条件的说法,如果某个类想要作为template的参数,它必须有满足template 表达式要求的接口)
    class Base
    {
        public:
            ~Base() = default;
            virtual void myPrint() = 0;
            int size() {return 111;}
    };
    
    class Derived: public Base
    {
        public:
        Derived() {}
        ~Derived() = default;
        virtual void myPrint()
        {
            std::cout << "Derived cout!!!" <<std::endl;
        }
    };
    
    void myPrint1(Derived &dd)
    {
        dd.myPrint();
    }
    
    template<typename T>
    void myPrint2(T t)
    {
        t.myPrint();
        t.size();
    }
    
    int main(int argc, char const *argv[])
    {
        Derived dd;
        myPrint1(dd);
        myPrint2(dd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    编译期多态:

    在编译时才能确定具体调用哪个函数

    #include 
    #include 
    
    // 编译期多态,个人理解就是:在编译时存在多个选择,根据参数类型的不同调用不同的函数。
    // 对于重载: 在编译时根据参数的不同,选择不同的函数。(这些个函数已经存在,选一个心仪的)
    // 对于function templates: 在编译期,根据不同的template参数具现出不同的函数(函数还没有,根据参数不同,现生成一个心仪的)
    template <typename T>
    T max1(const T &a, const T &b)
    {
        return a > b ? a : b;
    }
    
    int min1(const int &a, const int &b)
    {
        std::cout << "int min" << std::endl;
        return a < b ? a : b;
    }
    
    std::string min1(const std::string &a, const std::string &b)
    {
        std::cout << "string min" << std::endl;
        return a < b ? a : b;
    }
    
    int main(int argc, char const *argv[])
    {
        int a = 11, b = 12;
        std::string a1("hello world"), b1("hello");
    
        std::cout << max1(a, b) << std::endl;
        std::cout << max1(a1, b1) << std::endl;
        std::cout << min1(a, b) << std::endl;
        std::cout << min1(a1, b1) << std::endl;
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    运行期多态:

    在运行时才知道待用哪个函数。

    #include 
    
    // 运行时多态演示代码
    class Base
    {
        public:
            ~Base() = default;
            virtual void myPrint() = 0;
    };
    
    class Derived: public Base
    {
        public:
        Derived() {}
        ~Derived() = default;
        virtual void myPrint()
        {
            std::cout << "Derived cout!!!" <<std::endl;
        }
    };
    
    
    int main(int argc, char const *argv[])
    {
        Base *pb = new Derived;
    
        // 程序运行的时候,才能明确调用的时Derived的myPrint()
        pb->myPrint();
        delete pb;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    请记住:

    classes 和template都支持接口和多态

    对于classes而言接口是显式的,以函数签名为中心。多态则是通过virtual函数发生于运行期

    对于template参数而言,接口是隐式的,基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译器

    条款42:了解typename的双重意义

    请记住:

    声明template参数时,前缀关键字class和typename可互换

    请使用关键字表示嵌套从属类型名称;但不得在base class lists(基类列)或member initialization list(成员初值列)内以它作为base class修饰符。

    条款43: 学习处理模板化基类内的名称

    请记住:

    ​ 可在derived class templates内通过“this->”指涉base class templates内的成员名称,或藉由一个明白写出的“base class 资格修饰符”完成

    写一段:

    #include 
    #include 
    class CompanyA
    {
    public:
        CompanyA() {}
        ~CompanyA() = default;
        void sendClearText(const std::string &msg)
        {
            std::cout << "company A sendEncrypted msg = " + msg << std::endl;
        }
    
        void sendEncrypted(const std::string &msg)
        {
            std::cout << "company A sendEncrypted msg = " + msg << std::endl;
        }
    };
    
    class CompanyB
    {
    public:
        CompanyB() {}
        ~CompanyB() = default;
        void sendClearText(const std::string &msg)
        {
            std::cout << "company B sendEncrypted msg = " + msg << std::endl;
        }
    
        void sendEncrypted(const std::string &msg)
        {
            std::cout << "company B sendEncrypted msg = " + msg << std::endl;
        }
    };
    
    class MsgInfo
    {
    public:
        MsgInfo(const std::string &inmsg) : msg(inmsg) {}
        ~MsgInfo() = default;
        std::string getMsg() const
        {
            return msg;
        }
    
    private:
        std::string msg;
    };
    
    template <typename Company>
    class MsgSender
    {
    public:
        MsgSender() {}
        ~MsgSender() = default;
        void sendClear(const MsgInfo &info)
        {
            std::string msg = info.getMsg();
            Company c;
            c.sendClearText(msg);
        }
    
        void sendSecret(const MsgInfo &info)
        {
            std::string msg = info.getMsg();
            Company c;
            c.sendEncrypted(msg);
        }
    };
    
    // 以template为基类定义子类
    template <typename Company>
    class LoggingMsgSender : public MsgSender<Company>
    {
    public:
        LoggingMsgSender() {}
        ~LoggingMsgSender() = default;
    
        // 解决方案3:使用using声明式,假设sendClear在基类
        // using MsgSender::sendClear;
        void sendClearMsg(const MsgInfo &info)
        {
            std::cout << "adasd" << std::endl;
            // g++报错: there are no arguments to 'sendClear' that depend on a template parameter,
            // so a declaration of 'sendClear' must be available
            // 解决方案1:使用编译参数:“-fpermissive”,使用该参数后从报错变成了告警。
            // 解决方案2:使用this
            // this->sendClear(info);
            sendClear(info); //LoggingMsgSender在被实例化之前不知道sendClear在哪里。编译器也不会到base classes内查找。
            // 解决方案4: 明确指出被调用函数位于基类中(这个方案不推荐使用),如果sendClear是一个virtual,这样会关闭“virtual 绑定行为”
            // MsgSender::sendClear(info);
            std::cout << "adasdfffff" << std::endl;
        }
    };
    
    class CompanyZ
    {
    public:
        CompanyZ() {}
        ~CompanyZ() = default;
    
        void sendEncrypted(const std::string &msg)
        {
            std::cout << "company Z sendEncrypted msg = " + msg << std::endl;
        }
    };
    
    // template<>这个不是template也不是标准class,而是个特化版的MsgSender template,在template实参是CompanyZ时候使用。
    // 这就是模板全特化
    template <>
    class MsgSender<CompanyZ>
    {
    public:
        MsgSender() {}
        ~MsgSender() = default;
        void sendSecret(const MsgInfo &info)
        {
            std::string msg = info.getMsg();
            CompanyZ c;
            c.sendEncrypted(msg);
        }
    };
    
    int main(int argc, char const *argv[])
    {
        LoggingMsgSender<CompanyA> mca;
        mca.sendClear(std::string("hello"));
        mca.sendSecret(std::string("hello"));
    
        LoggingMsgSender<CompanyB> mcb;
        mcb.sendClear(std::string("hello"));
        mcb.sendSecret(std::string("hello"));
    
        // 模板全特化:针对某一个类型的全面特化。
        LoggingMsgSender<CompanyZ> mcz;
    
        // 特化版的MsgSender template中没有提供sendClear
        // mcz.sendClear(std::string("hello")); // 编译报错
        mcz.sendSecret(std::string("world"));
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140

    条款44: 将与参数无关的代码抽离templates

    请记住:

    ​ template生成多个 classes和多个函数,所以任何template代码都不应该于某个造成膨胀的template参数产生相依关系。

    ​ 因非模板参数而造成的代码膨胀,往往可以消除。做法是以函数参数或class成员变量替换template参数。

    ​ 因类型参数(type parameters)造成的代码膨胀,往往可以降低,做法是让带有完全相同二进制表述的具象类型实现共享代码。

    条款45:运用成员模板函数接受所有兼容类型

    请记住:

    ​ 请使用member function templates 生成“可接受所有兼容类型”的函数

    ​ 如果你声明member templates 用于“泛化构造”或“泛化assignment”,你还需要声明正常的copy构造函数和copy assignment函数。

    条款46:需要类型转换时请为模板定义非成员函数

    请记住:

    ​ 当我们编写一个class template,而它所提供的“与此template相关”函数支持“所有参数之隐式转换”时,请将那些函数定义为“class template内部的friend函数”。

    条款47:请使用traits class 表现类型信息

    请记住:

    ​ Traits classes使得“类型相关信息”在编译期可用。它们以templates和“”完成实现

    ​ 整合重载技术后,traits classes有可能在编译期对类型执行if…else测试。

    条款48:认识template元编程

    这个可能太高阶了,目前知道有这么个东西就行。光靠这里去理解可能有点难。

    什么是模板元编程(Template metaprogramming- TMP)?

    编写template-base C++程序并执行于编译期的过程

    TMP是被发现而不是发明出来的。

    使用TMP的好处?

    1.它让事情更容易

    2.某些错误可以在编译期就找出来

    3.可能在每一个方面都高效:较小的可执行文件、较短的运行期、较少的内存需求

    缺点:

    ​ 编译时间变长了

    难点:

    ​ 语法不直观,支持工具不充分。

    请记住:

    ​ TMP可将工作由运行期迁移到编译期,从而实现早期错误侦测和更高的执行效率

    ​ TMP可被用来生成“基于政策选择组合”(based on combinations of policy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。

  • 相关阅读:
    EasyRecovery2024破解版激活码
    5.部署web项目到云服务器
    (附源码)springboot农田灌溉设施管理系统 毕业设计 260931
    电磁仿真设计RMxprt-6p72s电励磁凸极同步电机分析案例
    [源码解析] NVIDIA HugeCTR,GPU 版本参数服务器---(7) ---Distributed Hash之前向传播
    [实践应用] 深度学习之损失函数
    基于单片机的感应自动门系统
    (九)springmvc+mybatis+dubbo+zookeeper分布式架构 整合 - maven构建ant-framework核心代码Base封装
    淘宝官方开放平台API接口获得店铺的所有商品、商品id、商品标题、销量参数调用示例
    使用Spring Boot和Kafka实现消息发送和订阅
  • 原文地址:https://blog.csdn.net/m0_37406848/article/details/132696208