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

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

隐式接口:

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

显式接口:

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

​ 在源码中明确可见。

cpp 复制代码
#include <iostream>

// 个人感觉隐式和显式接口只是站在了不同的角度看一个类的接口。
// 站在类的角度:
// 类中定义的接口是显示的

// 站在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;
}

编译期多态:

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

cpp 复制代码
#include <iostream>
#include <string>

// 编译期多态,个人理解就是:在编译时存在多个选择,根据参数类型的不同调用不同的函数。
// 对于重载: 在编译时根据参数的不同,选择不同的函数。(这些个函数已经存在,选一个心仪的)
// 对于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;
}

运行期多态:

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

cpp 复制代码
#include <iostream>

// 运行时多态演示代码
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;
}

请记住:

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 资格修饰符"完成

写一段:

cpp 复制代码
#include <iostream>
#include <string>
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<Company>::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<Company>::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;
}

条款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)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。

相关推荐
铁匠匠匠1 小时前
从零开始学数据结构系列之第六章《排序简介》
c语言·数据结构·经验分享·笔记·学习·开源·课程设计
C-SDN花园GGbond1 小时前
【探索数据结构与算法】插入排序:原理、实现与分析(图文详解)
c语言·开发语言·数据结构·排序算法
迷迭所归处2 小时前
C++ —— 关于vector
开发语言·c++·算法
架构文摘JGWZ3 小时前
Java 23 的12 个新特性!!
java·开发语言·学习
leon6253 小时前
优化算法(一)—遗传算法(Genetic Algorithm)附MATLAB程序
开发语言·算法·matlab
CV工程师小林3 小时前
【算法】BFS 系列之边权为 1 的最短路问题
数据结构·c++·算法·leetcode·宽度优先
小齿轮lsl3 小时前
PFC理论基础与Matlab仿真模型学习笔记(1)--PFC电路概述
笔记·学习·matlab
Aic山鱼3 小时前
【如何高效学习数据结构:构建编程的坚实基石】
数据结构·学习·算法
qq11561487073 小时前
Java学习第八天
学习
white__ice4 小时前
2024.9.19
c++