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

模板编程是一种隐式接口编译期多态技术,class则是显示接口,运行时多态

  • 对于class而言接口是显示的,以函数签名为中心,多态则是通过virtual函数发生在运行期。
  • 对于template而言,接口是隐式的,多态通过template具现化和函数重载解析发生在编译器

条款42 了解typename的双重意义

在模板中class和typename关键字没有区别,模板内的嵌套从属类型名称默认是被当成函数,只有在前面添加typename才会被当成一种类型。但是typename无法在成员初始化列表中作为base class修饰符,也无法出现在基类列表中。

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

class CompanyA {
    public:
        void sendCleartext(const std::string& msg);
        void sendEcrypted(const std::string& msg);
}

class CompanyB {
    public:
        void sendCleartext(const std::string& msg);
        void sendEcrypted(const std::string& msg);
}

class MsgInfo{....};
template<typename Company>
class MsgSender {
public:
    void sendClear(const MsgInfo& info)
    {
        std::string msg;
        Company c;
        c.sendCleartext(msg);
    }
    void sendSecret(const MsgInfo& info)
    {....}
};
上面这段代码看上去,工作良好,也的确如此。根据不同的Company,发送不同的消息。现在需要在发送消息的前后添加
一些日志,于是有了下面的这个派生类

template<typename Company>
class LoggingMsgSender : public MsgSender<Company>
{
    public:
        ....
        void sendClearMsg(const MsgInfo& info)
        {
            //发送前些写log
            sendClear(info);
            //发送后写log
        }      
};
为了避免名称遮掩,子类使用了另外一个名字。但是很可惜上面的代码无法编译通过,因为在调用sendClear的时候,
会报不存在的错误。很明显这个函数是在base class中的,是存在的,为什么这里会报不存在的错误呢。现在假想一种
场景,base class会被特化,很可能是一种完全特化的版本,那么这会导致base class没有sendClear这个接口,
因此C++就明确的拒绝这种行为,不去base class中查找指定的方法。 

为了解决上面提出的这个问题,C++中有三个方法可以解决,方法如下:

  • 明确使用this
this->sendClear(info);
  • using声明
public:
    ....
    using MsgSender<Company>::sendClear;
    void sendClearMsg(const MsgInfo& info)
    {
            //发送前些写log
            sendClear(info);
            //发送后写log
    }
  • 明确指出sendClear在base类中
MsgSender<Company>::sendClear(info);

但是上面的这个方法,存在一个问题,当sendClear是个虚函数的时候,会关闭virtual绑定行为。

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

C/C++语言中的指针做的很好的一件事就是支持隐式转换,Derived class指针可以隐式转换为base class指针,指向non-const对象的指针可以转换为指向const对象等等,下面是一个他们之间转换的例子:

class Top {....};
class Middle: public Top {...};
Top* pt1 = new Middle;
Top* pt2 = new Bottom;
const Top* pct2 = pt1;

上面的这种隐式转换得益于指针,但是不幸的是在C++中智能指针并不能自动去实现这种隐式转换,智能指针本身就是利用template去实现的,同一个template的不同具现体之间并不存在什么与生俱来的固有关系。例如下面的代码

template<typename T>
class SmartPtr {
    public:
        explicit SmartPtr<T* realPtr>;
        ....
};
SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle);
SmartPtr<Top> pt2 = SmartPtr<Middle>(new Bottom);
SmartPtr<const Top> pct2 = pt1; 

上面的代码无法编译通过, 所有Top和Middle有父子关系,如果是指针是可以隐式转换的,但是同一个template的不同具现体之间并不存在什么与生俱来的固有关系。 因此为了可以让上面的代码可以编译通过,需要给SmartPtr类提供兼容

  • 请使用member function templates(成员函数模板)生成,可接受所有兼容类型的函数
  • 如果你声明member function用于”泛化copy构造”或”泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符

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

只有non-member函数才有能力在所有的实参身上实施隐式类型转换,下面的例子演示了模板函数无法对所有实参实施隐式类型转换的场景。

template<typename T>
class Rational {
    Rational(const T& numerator = 0,
             const T& denominator = 1);
    const T numerator() const;
    const T denominator() const;
    .....
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,
                            const Rational<T>& rhs)
{ ...... }

上面的乘法操作符使用了模板来实现,为了可以对任意实参都具备隐式类型转换的能力,所以采用了non-member函数,具体细节可以参考条款24,尽管如此下面的代码仍然无法通过编译。

Rational<int> oneHalf(1,2);
Rational<int> result = oneHalf * 2; 

尽管可以通过oneHalf推导出第一个实参的类型是int,但是通过第二个参数无法推导出其类型,因为template函数在实参推导过程中从不进行隐式类型转换。为了解决这个问题可以通过在Rational加上一个friend函数声明即可解决问题,代码如下。

template<typename T>
class Rational {
public:
    .....
friend const Rational operator*(const Rational& lhs,
                                const Rational& rhs);
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,
                            const Rational<T>& rhs);

当Rational具现化后,对应的友元函数operator*也会具现化一份,那么就变成了一个non-member函数了,此时就可以做隐式类型转换了。尽管如此,上面的代码在链接阶段却无法链接,原因在于我们通过模板具现化在类的内部产生了一个operator*的友元函数声明,但是还没有其定义,我们期望可以通过类外的operator*函数模板为我们提供其定义,但是这行不通。为了解决这个问题,需要将operator*的定义式写在类的内部。将声明和定义放在一起。现在就可以正常编译和链接了。

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

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

简而言之就是在编译期获得类型信息,一般结合template function来完成针对不同的类型采取不同的操作这一功能。如果没有traits的话,那么这个template function会通过typeid得到类型信息,然后根据类型信息采取不同的操作,很显然typeid得到类型信息是一个运行期操作。因此有了traits classes,可以在编译期间获得类型信息。例如STL算法库中的advance算法,用于移动迭代器,对于某些迭代器只能每次移动一步,而对于随机迭代器可以一次移动N步,因此advance算法需要根据传入的迭代器类型采取不同的算法。为此我们需要给advance 提供一个traits classes。其实就是所有的自定义类型都提供一个typedef将自己的迭代器类型都通过typedef进行名称统一,然后traits classes再次通过typedef给这些自定义类型提供一个统一的名称,就这样就实现了编译器得到类型信息这个目的。但是advance还可以接收指针这种内置类型,我们没法给修改指针,让指针提供一个typedef,因此可以给advance提供一个针对指针的特化版本的实现。 

  • Traits classes使得”类型相关信息”在编译期可用,他们以templates和templates特化完成实现。
  • 整合重载技术后traits classes有可能在编译器对类型指向if..else测试

MySQL数据库从入门实战课

12-31
限时福利1:购课进答疑群专享柳峰(刘运强)老师答疑服务。 限时福利2:购课后添加学习助手(微信号:csdn590),按消息提示即可领取编程大礼包! 注意:原价129的课程,最后2天限时秒杀仅需49元!! 为什么说每一个程序员都应该学习MySQL? 根据《2019-2020年中国开发者调查报告》显示,超83%的开发者都在使用MySQL数据库。 使用量大同时,掌握MySQL早已是运维、DBA的必备技能,甚至部分IT开发岗位也要求对数据库使用和原理有深入的了解和掌握。 学习编程,你可能会犹豫选择 C++ 还是 Java;入门数据科学,你可能会纠结于选择 Python 还是 R;但无论如何, MySQL 都是 IT 从业人员不可或缺的技能! 【课程设计】 在本课程中,刘运强老师会结合自己十多年来对MySQL的心得体会,通过课程给你分享一条高效的MySQL入门捷径,让学员少走弯路,彻底搞懂MySQL。 本课程包含3大模块:  一、基础篇: 主要以最新的MySQL8.0安装为例帮助学员解决安装与配置MySQL的问题,并对MySQL8.0的新特性做一定介绍,为后续的课程展开做好环境部署。 二、SQL语言篇: 本篇主要讲解SQL语言的四大部分数据查询语言DQL,数据操纵语言DML,数据定义语言DDL,数据控制语言DCL,学会熟练对库表进行增删改查等必备技能。 三、MySQL进阶篇: 本篇可以帮助学员更加高效的管理线上的MySQL数据库;具备MySQL的日常运维能力,语句调优、备份恢复等思路。  
©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值