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

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

class CompanyA {
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<typename Company>
class MsgSender {
public:
    ...                     // 构造函数、析构函数等等。
    void sendClear(const MsgInfo& info)
    {
        std::string msg;
        // 这儿,根据info产生信息
        Company c;
        c.sendCleartext(msg);
    }
    void sendSecret(const MsgInfo& info)    // 类似sendClear,唯一不同是
    { ... }                                 // 这里调用c.sendEncrypted
};

        这个做法行得通。但假设我们有时候想要在每次送出信息时记录(log)某些信息。derived class可轻易加上这样的生产力,那似乎是个合情合理的解法:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
    ...                                         // 构造函数、析构函数等等。
    void sendClearMsg(const MsgInfo& info)
    {
        // 将“传送前”的信息写至log;
        sendClear(info);               // 调用base class函数:这段代码无法通过编译
        // 将“传送后”的信息写至log;
    }
    ...
};

        注意这个derived class的信息传送函数有一个不同的名称(sendClearMsg),与其base class内的名称(sendClear)不同。那是个好设计,因为它避免遮掩“继承而来的函数名称”(见条款33),也避免重新定义一个继承而得的non-virtual函数(见条款36)。但上述代码无法通过编译,至少对严守规律的编译器而言。这样的编译器会抱怨sendClear不存在。我们的眼睛可以看到sendClear的确在base class内,编译器却看不到它们。为什么?

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

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

class CompanyZ {      // 这个class不提供
public:               // sendCleartext函数
    ...
    void sendEncrypted(const std::string& msg);
    ...
};

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

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

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

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

template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
    ...                                         
    void sendClearMsg(const MsgInfo& info)
    {
        // 将“传送前”的信息写至log;
        sendClear(info);               // 如果Company == CompanyZ,这个函数不存在。
        // 将“传送后”的信息写至log;
    }
    ...
};

        正如注释所言,当base class被指定为MsgSender<CompanyZ>时这段代码不合法,因为那个class并未提供sendClear函数!那就是为什么C++拒绝这个调用的原因:它指定base class templates有可能被特化,而那个特化版本可能不通过和一般性template相同的接口。因此它往往拒绝在templatized base classes(模板化基类,本例的msgSender<Company>)内寻找继承而来的名称(本例的SendClear)。就某种意义而言,当我们从Object Oriented C++跨进Template C++(见条款1),继承就不像以前那般畅行无阻了。

        为了重头来过,我们必须有某种办法令C++“不进入templatized base classes观察”的行为失效。有三个办法,第一是在base class函数调用动作之前加上“this->”:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
    ...                                         
    void sendClearMsg(const MsgInfo& info)
    {
        // 将“传送前”的信息写至log;
        this->sendClear(info);               // 成立,假设sendClear将被继承。
        // 将“传送后”的信息写至log;
    }
    ...
};

        第二是使用using声明式。如果你已读过条款33,这个解法应该令你感到熟悉。

template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
    using MsgSender<Company>::sendClear;    // 告诉编译器,请它假设sendClear位于base class内。
    ...                                         
    void sendClearMsg(const MsgInfo& info)
    {
        ...
        sendClear(info);               // 成立,假设sendClear将被继承。
        ...
    }
    ...
};

        第三个做法是,明白指出被调用的函数位于base class内:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
    ...                                         
    void sendClearMsg(const MsgInfo& info)
    {
        ...
        MsgSender<Company>::sendClear(info);      // 成立,假设sendClear将被继承。
        ...
    }
    ...
};

        这个解法并不让人满意,因为如果被调用的virtual函数,上述的明确资格修饰会关闭“virtual绑定行为”。

请记住

  • 可在derived class templates内通过“this->”指涉base class templates内的成员名称,或籍由一个明白写出的“base class资格修饰符”完成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值