Skip to content

Latest commit

 

History

History
156 lines (133 loc) · 4.2 KB

File metadata and controls

156 lines (133 loc) · 4.2 KB

条款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);
  ...
};
...
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) {...}
};

这个做法可行。假设我们有时候想要在每次送出信息时log某些信息。派生类可轻易加上这样的功能:

template <typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
  ...
  void sendClearMsg(const MsgInfo& info) {
    // 输出发送前日志信息
    sendClear(info);  // 调用基类函数,这段代码无法通过编译
    // 输出发送后日志信息
  }
  ...
};

这段代码编译错误,当编译期看到类模板LoggingMsgSender时,并不知道它继承的是什么样的class。在LoggingMsgSender被实例化之前,无法确切知道MsgSender的模板参数类型,也就无法知道它是否有个sendClear参数。

假设我们有个class CompanyZ使用加密通讯:

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

我们可以对CompanyZ产生一个MsgSender特化模板:

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

让我们再次考虑派生类LoggingMsgSender:

template <typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
  ...
  void sendClearMsg(const MsgInfo& info) {
    // 输出发送前日志信息
    sendClear(info);  // 调用基类函数,这段代码无法通过编译
    // 输出发送后日志信息
  }
  ...
};

当基类被指定为 MsgSender<CompanyZ> 时这段代码不合法,因为它并未提供sendClear函数。C++往往拒绝在模板化基类(MagSender<Company>)内寻找继承而来的名称,因为可能存在全特化版本的基类不提供一般template相同的接口。

我们必须有某种办法令C++不进入模板化基类的观察行为失效。有三个办法,第一是在基类函数的调用操作前加上 this->

template <typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
  ...
  void sendClearMsg(const MsgInfo& info) {
    // 输出发送前日志信息
    this->sendClear(info);  // ok
    // 输出发送后日志信息
  }
  ...
};

第二种办法是使用 using 声明,它将被遮蔽的基类名字带入一个派生类作用域内。但是这里并不是名字遮蔽问题,而是编译器不进入基类作用域内查找名字:

template <typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
  using MsgSender<Company>::sendClear;
  ...
  void sendClearMsg(const MsgInfo& info) {
    // 输出发送前日志信息
    sendClear(info);  // ok
    // 输出发送后日志信息
  }
  ...
};

第三个做法是,指明被调用函数位于基类内:

template <typename Company>
class LoggingMsgSender : public MsgSender<Company> {
public:
  ...
  void sendClearMsg(const MsgInfo& info) {
    // 输出发送前日志信息
    MsgSender<Company>::sendClear(info);  // ok
    // 输出发送后日志信息
  }
  ...
};

但是此方法会阻止virtual绑定行为。

上述做法的本质都相同:对编译期承诺基类模板的每一个版本都将支持一般版本所提供的接口。如果稍后的源码含有这个:

LoggingLsgSender<CompanyZ> zMsgSender;
MsgInfo msgData;
...
zMsgSender.sendClearMsg(msgData);  // error!

对sendClearMsg的调用操作将无法通过编译。

请记住

  • 可在派生类模板种通过 this-> 指出基类模板内的成员名称,或借由显式的基类作用域修饰符完成。