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

来源:互联网 发布:apache shirt session 编辑:程序博客网 时间:2024/04/30 10:33

[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资格修饰符完成。
1 0
原创粉丝点击