《Effective C++》:条款43:学习处理模板化基类内的名称

[toc]
模板化的类作为基类时,有哪些要注意的地方。以一个例子说明,假设现在编写一个发送信息到不同公司的程序,信息要么译成密码,要么就是原始文字,在编译期间来决定哪一家公司发送至哪一家公司,采用template手法:

    class CompanyA{
    public:
        void sendCleartext(const std::string& msg);
        void sendEncryted(const std::string& msg);
        ……
    };
    class CompanyB{
    public:
        void sendCleartext(const std::string& msg);
        void sendEncryted(const std::string& msg);
        ……
    };
    ……//还有一些公司

    class MsgInfo{……};//用来保存信息,以备将来产生信息

    template<typename Company>
    class MsgSender{
    public:
        ……//构造析构等函数
        void sendClear(const MsgInfo& info)
        {
            std::string msg;
            //根据info产生信息
            Company c;
            c.sendCleartext(msg);
        }
        void sendSecart(const MsgInfo& info)
        {……}
    };

上面这个做法行得通,但是如果要在每次送出信息时记录日志,可以派生出derived class,加上记录的日志

    template<typename Company>
    class LoggingMsgSender: public MsgSender<Company>
    {
    public:
        ……//析构构造等
        void SendClearMsg(const MsgInfo& info)
        {
            //发送前的信息写到log
            sendClear(info);//调用base class函数,这段代码无法通过编译
            //传送后信息写到log
        }
    };

在派生类中,sendClearMsg和base class中的sendClear不同,这是个好设计,避免遮掩继承而得的名称,也避免了重新定义一个继承而得non-virtual函数。但是编译不能通过,因为编译器看不到sendClear函数。

因为当编译器遇到class template LoggingMsgSender定义式时,不知道它继承什么样的class,因为MsgSender中的Company是个参数,在LoggingMsgSender被具体化之前,无法确切知道它是什么,自然而然就不知道class MsgSender是什么,也就不知道它是否有个sendClear函数。(备注,在g++中可以使用参数 -fpermissive参数来通过编译,但是不建议使用)

为了让问题更具体化,假设现在有个class CompanyZ坚持使用加密通讯

    class CompanyZ{
    pubic:
        void sendEncryted(const std::sting& msg);
        ……
    };

CompanyZ没有sendClear函数,一般性的MsgSender template对CompanyZ并不合适,这时我们可以针对CompanyZ产生一个MsgSender特化版

    template<>
    class MsgSender<CompanyZ>{
    public:
        void sendSecret(const MsgInfo& infof)
        {……}
        ……
    };

开头的template<>表示是特化版的MsgSender template,在template实参是CompanyZ时被使用。这就是所谓的模板全特化(total template specialization):template MsgSender针对类型CompanyZ特化了,且是全面性特化,即一旦类型参数为CompanyZ,没有其他template参数可供变化了。

再来看一下刚刚的LoggingMsgSender

    template<typename Company>
    class LoggingMsgSender: public MsgSender<Company>
    {
    public:
        ……//析构构造等
        void SendClearMsg(const MsgInfo& info)
        {
            //发送前的信息写到log
            sendClear(info);//如实Company是CompanyZ,那么这个函数不存在
            //传送后信息写到log
        }
    };

如果Company=CompanyZ,那么sendClear函数就不存在。C++拒绝调用这个函数是因为它知道base class template可能被特化,而那个特化版本可能不提供和一般性template相同的接口。所以它往往拒绝在templatized base classes(模板化基类)中寻找继承而来的名称。当我们从Object Oriented C++跨进到Template C++时,继承不像以前那样畅行无阻了。

现在应该讨论一下怎么解决上面不能通过编译的问题了。我们必须有某种办法令C++“不进入templatized base class观察”的行为失效。有一下三种办法

1、在base class函数调用动作之前加上“this->”

    template<typename Company>
    class LoggingMsgSender: public MsgSender<Company>
    {
    public:
        ……//析构构造等
        void SendClearMsg(const MsgInfo& info)
        {
            //发送前的信息写到log
            this->sendClear(info);
            //传送后信息写到log
        }
    };

2、使用using声明式,有点类似**条款**33。

    template<typename Company>
    class LoggingMsgSender: public MsgSender<Company>
    {
    public:
        ……//析构构造等
        uinsg MsgSender<Company>::sendClear;//告诉编译器,假设sendClear位于base class内
        void SendClearMsg(const MsgInfo& info)
        {
            //发送前的信息写到log
            sendClear(info);//可以编译通过,假设sendClear将被继承
            //传送后信息写到log
        }
    };

补充一下,这里情况和**条款**33不同,这里不是将被掩盖的base class名称带入一个derived class作用域内,而是编译器不进入base class作用域内查找,通过using告诉编译器,请它去查找。

3、明白指出被调用的函数位于base class内

    template<typename Company>
    class LoggingMsgSender: public MsgSender<Company>
    {
    public:
        ……//析构构造等
        void SendClearMsg(const MsgInfo& info)
        {
            //发送前的信息写到log
            MsgSender<Company>::sendClear(info);//可以编译通过,假设sendClear被继承
            //传送后信息写到log
        }
    };

这种做法使用了明确资格修饰符(explicit qualification),这将会关闭virtual绑定行为。

回头再看上面三种做法,原理相同:对编译器承诺“base class template的任何特化版本都将支持其一般(泛化)版本所提供的接口”。这样的承诺是编译器在解析(parse)像LoggingMsgSender这样的derived class template时需要的。但如果这个承诺稍后没有兑现,编译器还会给事实一个公道。例如,稍后源码内:

    LoggingMsgSender<CompanyZ> zMsgSender;
    MsgInfo msgData;
    zMsgSender.sendClearMsg(msgData);//错误,无法编译通过

在调用sendClearMsg这个点上,编译器直到base class是个template特化版本,且它直到这个特化版不提供sendClearMsg函数。

总结一下,本条款讨论的是,面对指涉base class member之无效references,编译器的诊断时间可能发生在早起(当解析derived class template定义时),也可能发生在晚期(当template被特定值template实参具体化时)。C++宁愿较早诊断,这也是为什么当base classes从template具体化时,它假设对那些base classes的内容毫无所悉的缘故。

总结

  • 可在derived class templates内通过this->指涉base class templates内的成员名称,或藉由一个明白写出base class资格修饰符完成。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值