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

1.前言

假设我们需要编写一个程序,它能够传送信息到若干不同的公司去。信息要不编译称密码,要不就是未加工的文字。如果编译器间我们有足够的信息来决定哪一个信息传至哪一家公司,就可以采用基于template的解法

class Company{

    public:
        ...
        void sendClearText(const std::string& msg);
        void sendEncrypted(const std::string& msg);
        ...

};
class CompanyB{

    public:
        ...
        void sendCleartext(const std::string& msg);
        void sendEncrypted(const std::string& msg);
        ...
};
...//针对其它公司设计的classes
class MsgInfo{

    ....//这个class用来保存信息,以备将来产生信息
};

template
class MsdSender{

    class MsgSender{
        public:
            ...
            void sendClear(const MsgInfo& info)
            {
                std::string msg;
                Company c;    
                c.sendCleartext(msg);
            }
            void sendSecret(const MsgInfo& info)//类似sendClear
            {
                ....
            }

    }
};

2.实例分析

这个做法能够完成任务,但假设我们有时候想要在每次送出信息时log某些信息。derived class可轻易加上这样的功能:

template
class LoggingMsgSender:public MsgSender
{

    public:
        ...//相关构造函数,析构函数等
        void sendClearMsg(const MsgInfo& info)
        {
            将“传送前”的信息写至log
            sendClear(info);//调写base class函数;这段代码无法通过编译
            将“传送后”的信息写至log            
        }
        ...
};

注意这里derived class的信息传送函数有一个不同的名称(sendClearMsg),与其base class内的名称(sendClear)不同。(能避免遮掩“继承而得”的名称,也避免重新定义一个继承而得得non-virtual函数)。然而不幸得时上述代码无法通过编译。编译器会抱怨sendClear不存在。我们能看到sendClear()确实在base class内,编译器却检测不到它们,为什么?

问题在于:当编译器遇到class template LoggingMsgSender定义式时,并不知道它要继承什么样的class。当然它继承的是MsgSender,但其中的Company是个template参数,不到loggingMsgSender被具现化,无法知道它是什么。而如果不知道Company是什么,就无法知道class MsgSender看起来是什么-更明确地说是没办法知道它是否有个sendClear函数。

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

class CompanyZ{

    public:
        ...
        void sendEncrypted(const std::string& msg);
        ...
};

一般性的MsgSender template对CompanyZ并不合适,因为那个template提供了一个sendClear函数(其中针对其类型参数Company调用了sendCleartext函数),而这对CompanyZ对象并不合理,要矫正这个问题,我们可以针对CompanyZ产生一个MsgSender特化版:

template<>
class MsgSender//一个全特化的MsghSender,
                        //它和一般template相同,差别在于它删除了sendClear
{
    public:
        ...
        void sendSecret(const MsgInfo& info)
        {...}
    
};

注意class定义式最前头的“template”语法象征这即不是template,也不是标准的class,而是个特化版的MsgSender template,在template实参是CompanyZ时被使用。而这个所谓的模板全特化(total template specialization):template MsgSender针对类型CompanyZ特化了而且其特性也是全面性的,也就是说一旦类型参数被定义为CompanyZ,再没有其它template参数可供变化。

现在,MsgSender针对CompanyZ进行了全特化,让我们再次考虑derived class 中的LoggingMsgSender:

templateMsgSender:
template
class LoggingMsgSender:public MsgSender
{

    public:
        ...
        void sendClearMsg(const MsgInfo& info)
        {
            //将“传送前”的信号写至log;
            sendClear(info);//如果·Company==CompanyZ,这个函数将不存在
            //将“传送后”的信息写到log
        }
        ....
};

正如注释所言,当base class被指定为MsgSender时,这段代码不合法,因为class并未提供sendClear函数,那就是为什么C++拒绝调用的原因:它知道base class template有可能被特化,而那个版本可能不提供和一般性template相同的接口。因此它会拒绝在template base classes(模板化基类,本例的MsgSender)内寻找继承而来的名称(本例的SendClear)。

为了重头来过,我们必须有某种办法令C++“不进入templatized base classes观察“的行为失效。有三个办法:

第一是在base class函数调用动作之前加上”->this“:

template
class LoggingMsg`Sender:public MsgSender
{

    public:
        ...
        void sendClearMsg(const MsgInfo& info)
        {
            //将”传送前“的休息写至Log;
            this->sendClear(info);//成立,假设sendClear被继承
            //将"传递后"的信息写至log;
        }
};

第二是使用using声明式。(条款33描述了using 声明式如何将”被掩盖的base class名称带入一个derived class作用域内“),我们可以写下sendClearMsg:

template
class LoggingMsgSender:public MsgSender
{

    public:
        using MsgSender::sendClear;//告诉编译器,请他假设sendClear位于base class内
        ...
        void sendClearMsg(const MsgInfo& info)
        {
            ...
            sendClear(info);//ok,假设sendClear将被继承
            ...
        }
};

虽然using声明式在这里或在条款33都可有效运作,但两处解决问题的本质其实不相同。这里的情况并不是base class名称被derived class名称遮掩,而是编译器不进入base class作用域查找,于是我们通过Using告诉它。

第三个方法是指出被调用的函数位于base class内:

template
class LoggingMsgSender:public MsgSender
{
    public:
        ...
        void sendClearMsg(const MsgInfo& info)
        {
            ...
            MsgSender::sendClear(info);//ok,假设sendClear将被继承
        }
        ....

};

但这不是一种很满意的方法,因为如果被调用的是virtual函数,上述的明确资格修斯(explicit qualification)会关闭”virtual绑定行为“。

你可能感兴趣的:(c++,开发语言)